diff options
author | alexv-smirnov <alex@ydb.tech> | 2022-12-20 00:50:48 +0300 |
---|---|---|
committer | alexv-smirnov <alex@ydb.tech> | 2022-12-20 00:50:48 +0300 |
commit | 84f2cfa253cc618438ed6e9d68b33fa7c0d88cb9 (patch) | |
tree | f0cf2236e0aafb3e437199f1ac7b559e7fad554a /contrib/libs/postgresql/src/backend/port | |
parent | bde6febc1ad3b826e72746de21d7250803e8e0b5 (diff) | |
download | ydb-84f2cfa253cc618438ed6e9d68b33fa7c0d88cb9.tar.gz |
add windows platform to ydb github export
Diffstat (limited to 'contrib/libs/postgresql/src/backend/port')
-rw-r--r-- | contrib/libs/postgresql/src/backend/port/win32/crashdump.c | 183 | ||||
-rw-r--r-- | contrib/libs/postgresql/src/backend/port/win32/signal.c | 344 | ||||
-rw-r--r-- | contrib/libs/postgresql/src/backend/port/win32/socket.c | 700 | ||||
-rw-r--r-- | contrib/libs/postgresql/src/backend/port/win32/timer.c | 121 | ||||
-rw-r--r-- | contrib/libs/postgresql/src/backend/port/win32_sema.c | 235 | ||||
-rw-r--r-- | contrib/libs/postgresql/src/backend/port/win32_shmem.c | 607 |
6 files changed, 2190 insertions, 0 deletions
diff --git a/contrib/libs/postgresql/src/backend/port/win32/crashdump.c b/contrib/libs/postgresql/src/backend/port/win32/crashdump.c new file mode 100644 index 0000000000..45b6696ba1 --- /dev/null +++ b/contrib/libs/postgresql/src/backend/port/win32/crashdump.c @@ -0,0 +1,183 @@ +/*------------------------------------------------------------------------- + * + * crashdump.c + * Automatic crash dump creation for PostgreSQL on Windows + * + * The crashdump feature traps unhandled win32 exceptions produced by the + * backend, and tries to produce a Windows MiniDump crash + * dump for later debugging and analysis. The machine performing the dump + * doesn't need any special debugging tools; the user only needs to send + * the dump to somebody who has the same version of PostgreSQL and has debugging + * tools. + * + * crashdump module originally by Craig Ringer <ringerc@ringerc.id.au> + * + * LIMITATIONS + * =========== + * This *won't* work in hard OOM situations or stack overflows. + * + * For those, it'd be necessary to take a much more complicated approach where + * the handler switches to a new stack (if it can) and forks a helper process + * to debug it self. + * + * POSSIBLE FUTURE WORK + * ==================== + * For bonus points, the crash dump format permits embedding of user-supplied + * data. If there's anything else that should always be supplied with a crash + * dump (postgresql.conf? Last few lines of a log file?), it could potentially + * be added, though at the cost of a greater chance of the crash dump failing. + * + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/port/win32/crashdump.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#define WIN32_LEAN_AND_MEAN + +/* + * Some versions of the MS SDK contain "typedef enum { ... } ;" which the MS + * compiler quite sanely complains about. Well done, Microsoft. + * This pragma disables the warning just while we include the header. + * The pragma is known to work with all (as at the time of writing) supported + * versions of MSVC. + */ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4091) +#endif +#include <dbghelp.h> +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/* + * Much of the following code is based on CodeProject and MSDN examples, + * particularly + * http://www.codeproject.com/KB/debug/postmortemdebug_standalone1.aspx + * + * Useful MSDN articles: + * + * http://msdn.microsoft.com/en-us/library/ff805116(v=VS.85).aspx + * http://msdn.microsoft.com/en-us/library/ms679294(VS.85).aspx + * + * Other useful articles on working with minidumps: + * http://www.debuginfo.com/articles/effminidumps.html + */ + +typedef BOOL (WINAPI * MINIDUMPWRITEDUMP) (HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam +); + + +/* + * This function is the exception handler passed to SetUnhandledExceptionFilter. + * It's invoked only if there's an unhandled exception. The handler will use + * dbghelp.dll to generate a crash dump, then resume the normal unhandled + * exception process, which will generally exit with an error message from + * the runtime. + * + * This function is run under the unhandled exception handler, effectively + * in a crash context, so it should be careful with memory and avoid using + * any PostgreSQL functions. + */ +static LONG WINAPI +crashDumpHandler(struct _EXCEPTION_POINTERS *pExceptionInfo) +{ + /* + * We only write crash dumps if the "crashdumps" directory within the + * postgres data directory exists. + */ + DWORD attribs = GetFileAttributesA("crashdumps"); + + if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) + { + /* 'crashdumps' exists and is a directory. Try to write a dump' */ + HMODULE hDll = NULL; + MINIDUMPWRITEDUMP pDump = NULL; + MINIDUMP_TYPE dumpType; + char dumpPath[_MAX_PATH]; + HANDLE selfProcHandle = GetCurrentProcess(); + DWORD selfPid = GetProcessId(selfProcHandle); + HANDLE dumpFile; + DWORD systemTicks; + struct _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + + ExInfo.ThreadId = GetCurrentThreadId(); + ExInfo.ExceptionPointers = pExceptionInfo; + ExInfo.ClientPointers = FALSE; + + /* Load the dbghelp.dll library and functions */ + hDll = LoadLibrary("dbghelp.dll"); + if (hDll == NULL) + { + write_stderr("could not load dbghelp.dll, cannot write crash dump\n"); + return EXCEPTION_CONTINUE_SEARCH; + } + + pDump = (MINIDUMPWRITEDUMP) (pg_funcptr_t) GetProcAddress(hDll, "MiniDumpWriteDump"); + + if (pDump == NULL) + { + write_stderr("could not load required functions in dbghelp.dll, cannot write crash dump\n"); + return EXCEPTION_CONTINUE_SEARCH; + } + + /* + * Dump as much as we can, except shared memory, code segments, and + * memory mapped files. Exactly what we can dump depends on the + * version of dbghelp.dll, see: + * http://msdn.microsoft.com/en-us/library/ms680519(v=VS.85).aspx + */ + dumpType = MiniDumpNormal | MiniDumpWithHandleData | + MiniDumpWithDataSegs; + + if (GetProcAddress(hDll, "EnumDirTree") != NULL) + { + /* If this function exists, we have version 5.2 or newer */ + dumpType |= MiniDumpWithIndirectlyReferencedMemory | + MiniDumpWithPrivateReadWriteMemory; + } + + systemTicks = GetTickCount(); + snprintf(dumpPath, _MAX_PATH, + "crashdumps\\postgres-pid%0i-%0i.mdmp", + (int) selfPid, (int) systemTicks); + dumpPath[_MAX_PATH - 1] = '\0'; + + dumpFile = CreateFile(dumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + NULL); + if (dumpFile == INVALID_HANDLE_VALUE) + { + write_stderr("could not open crash dump file \"%s\" for writing: error code %lu\n", + dumpPath, GetLastError()); + return EXCEPTION_CONTINUE_SEARCH; + } + + if ((*pDump) (selfProcHandle, selfPid, dumpFile, dumpType, &ExInfo, + NULL, NULL)) + write_stderr("wrote crash dump to file \"%s\"\n", dumpPath); + else + write_stderr("could not write crash dump to file \"%s\": error code %lu\n", + dumpPath, GetLastError()); + + CloseHandle(dumpFile); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + + +void +pgwin32_install_crashdump_handler(void) +{ + SetUnhandledExceptionFilter(crashDumpHandler); +} diff --git a/contrib/libs/postgresql/src/backend/port/win32/signal.c b/contrib/libs/postgresql/src/backend/port/win32/signal.c new file mode 100644 index 0000000000..580a517f3f --- /dev/null +++ b/contrib/libs/postgresql/src/backend/port/win32/signal.c @@ -0,0 +1,344 @@ +/*------------------------------------------------------------------------- + * + * signal.c + * Microsoft Windows Win32 Signal Emulation Functions + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/port/win32/signal.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/pqsignal.h" + +/* + * These are exported for use by the UNBLOCKED_SIGNAL_QUEUE() macro. + * pg_signal_queue must be volatile since it is changed by the signal + * handling thread and inspected without any lock by the main thread. + * pg_signal_mask is only changed by main thread so shouldn't need it. + */ +volatile int pg_signal_queue; +int pg_signal_mask; + +HANDLE pgwin32_signal_event; +HANDLE pgwin32_initial_signal_pipe = INVALID_HANDLE_VALUE; + +/* + * pg_signal_crit_sec is used to protect only pg_signal_queue. That is the only + * variable that can be accessed from the signal sending threads! + */ +static CRITICAL_SECTION pg_signal_crit_sec; + +/* Note that array elements 0 are unused since they correspond to signal 0 */ +static pqsigfunc pg_signal_array[PG_SIGNAL_COUNT]; +static pqsigfunc pg_signal_defaults[PG_SIGNAL_COUNT]; + + +/* Signal handling thread functions */ +static DWORD WINAPI pg_signal_thread(LPVOID param); +static BOOL WINAPI pg_console_handler(DWORD dwCtrlType); + + +/* + * pg_usleep --- delay the specified number of microseconds, but + * stop waiting if a signal arrives. + * + * This replaces the non-signal-aware version provided by src/port/pgsleep.c. + */ +void +pg_usleep(long microsec) +{ + Assert(pgwin32_signal_event != NULL); + if (WaitForSingleObject(pgwin32_signal_event, + (microsec < 500 ? 1 : (microsec + 500) / 1000)) + == WAIT_OBJECT_0) + { + pgwin32_dispatch_queued_signals(); + errno = EINTR; + return; + } +} + + +/* Initialization */ +void +pgwin32_signal_initialize(void) +{ + int i; + HANDLE signal_thread_handle; + + InitializeCriticalSection(&pg_signal_crit_sec); + + for (i = 0; i < PG_SIGNAL_COUNT; i++) + { + pg_signal_array[i] = SIG_DFL; + pg_signal_defaults[i] = SIG_IGN; + } + pg_signal_mask = 0; + pg_signal_queue = 0; + + /* Create the global event handle used to flag signals */ + pgwin32_signal_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (pgwin32_signal_event == NULL) + ereport(FATAL, + (errmsg_internal("could not create signal event: error code %lu", GetLastError()))); + + /* Create thread for handling signals */ + signal_thread_handle = CreateThread(NULL, 0, pg_signal_thread, NULL, 0, NULL); + if (signal_thread_handle == NULL) + ereport(FATAL, + (errmsg_internal("could not create signal handler thread"))); + + /* Create console control handle to pick up Ctrl-C etc */ + if (!SetConsoleCtrlHandler(pg_console_handler, TRUE)) + ereport(FATAL, + (errmsg_internal("could not set console control handler"))); +} + +/* + * Dispatch all signals currently queued and not blocked + * Blocked signals are ignored, and will be fired at the time of + * the pqsigsetmask() call. + */ +void +pgwin32_dispatch_queued_signals(void) +{ + int exec_mask; + + Assert(pgwin32_signal_event != NULL); + EnterCriticalSection(&pg_signal_crit_sec); + while ((exec_mask = UNBLOCKED_SIGNAL_QUEUE()) != 0) + { + /* One or more unblocked signals queued for execution */ + int i; + + for (i = 1; i < PG_SIGNAL_COUNT; i++) + { + if (exec_mask & sigmask(i)) + { + /* Execute this signal */ + pqsigfunc sig = pg_signal_array[i]; + + if (sig == SIG_DFL) + sig = pg_signal_defaults[i]; + pg_signal_queue &= ~sigmask(i); + if (sig != SIG_ERR && sig != SIG_IGN && sig != SIG_DFL) + { + LeaveCriticalSection(&pg_signal_crit_sec); + sig(i); + EnterCriticalSection(&pg_signal_crit_sec); + break; /* Restart outer loop, in case signal mask or + * queue has been modified inside signal + * handler */ + } + } + } + } + ResetEvent(pgwin32_signal_event); + LeaveCriticalSection(&pg_signal_crit_sec); +} + +/* signal masking. Only called on main thread, no sync required */ +int +pqsigsetmask(int mask) +{ + int prevmask; + + prevmask = pg_signal_mask; + pg_signal_mask = mask; + + /* + * Dispatch any signals queued up right away, in case we have unblocked + * one or more signals previously queued + */ + pgwin32_dispatch_queued_signals(); + + return prevmask; +} + + +/* + * Unix-like signal handler installation + * + * Only called on main thread, no sync required + */ +pqsigfunc +pqsignal(int signum, pqsigfunc handler) +{ + pqsigfunc prevfunc; + + if (signum >= PG_SIGNAL_COUNT || signum < 0) + return SIG_ERR; + prevfunc = pg_signal_array[signum]; + pg_signal_array[signum] = handler; + return prevfunc; +} + +/* Create the signal listener pipe for specified PID */ +HANDLE +pgwin32_create_signal_listener(pid_t pid) +{ + char pipename[128]; + HANDLE pipe; + + snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%u", (int) pid); + + pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, 16, 16, 1000, NULL); + + if (pipe == INVALID_HANDLE_VALUE) + ereport(ERROR, + (errmsg("could not create signal listener pipe for PID %d: error code %lu", + (int) pid, GetLastError()))); + + return pipe; +} + + +/* + * All functions below execute on the signal handler thread + * and must be synchronized as such! + * NOTE! The only global variable that can be used is + * pg_signal_queue! + */ + + +/* + * Queue a signal for the main thread, by setting the flag bit and event. + */ +void +pg_queue_signal(int signum) +{ + Assert(pgwin32_signal_event != NULL); + if (signum >= PG_SIGNAL_COUNT || signum <= 0) + return; /* ignore any bad signal number */ + + EnterCriticalSection(&pg_signal_crit_sec); + pg_signal_queue |= sigmask(signum); + LeaveCriticalSection(&pg_signal_crit_sec); + + SetEvent(pgwin32_signal_event); +} + +/* Signal handling thread */ +static DWORD WINAPI +pg_signal_thread(LPVOID param) +{ + char pipename[128]; + HANDLE pipe = pgwin32_initial_signal_pipe; + + /* Set up pipe name, in case we have to re-create the pipe. */ + snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\pgsignal_%lu", GetCurrentProcessId()); + + for (;;) + { + BOOL fConnected; + + /* Create a new pipe instance if we don't have one. */ + if (pipe == INVALID_HANDLE_VALUE) + { + pipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, 16, 16, 1000, NULL); + + if (pipe == INVALID_HANDLE_VALUE) + { + write_stderr("could not create signal listener pipe: error code %lu; retrying\n", GetLastError()); + SleepEx(500, FALSE); + continue; + } + } + + /* + * Wait for a client to connect. If something connects before we + * reach here, we'll get back a "failure" with ERROR_PIPE_CONNECTED, + * which is actually a success (way to go, Microsoft). + */ + fConnected = ConnectNamedPipe(pipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + if (fConnected) + { + /* + * We have a connection from a would-be signal sender. Process it. + */ + BYTE sigNum; + DWORD bytes; + + if (ReadFile(pipe, &sigNum, 1, &bytes, NULL) && + bytes == 1) + { + /* + * Queue the signal before responding to the client. In this + * way, it's guaranteed that once kill() has returned in the + * signal sender, the next CHECK_FOR_INTERRUPTS() in the + * signal recipient will see the signal. (This is a stronger + * guarantee than POSIX makes; maybe we don't need it? But + * without it, we've seen timing bugs on Windows that do not + * manifest on any known Unix.) + */ + pg_queue_signal(sigNum); + + /* + * Write something back to the client, allowing its + * CallNamedPipe() call to terminate. + */ + WriteFile(pipe, &sigNum, 1, &bytes, NULL); /* Don't care if it + * works or not */ + + /* + * We must wait for the client to read the data before we can + * disconnect, else the data will be lost. (If the WriteFile + * call failed, there'll be nothing in the buffer, so this + * shouldn't block.) + */ + FlushFileBuffers(pipe); + } + else + { + /* + * If we fail to read a byte from the client, assume it's the + * client's problem and do nothing. Perhaps it'd be better to + * force a pipe close and reopen? + */ + } + + /* Disconnect from client so that we can re-use the pipe. */ + DisconnectNamedPipe(pipe); + } + else + { + /* + * Connection failed. Cleanup and try again. + * + * This should never happen. If it does, there's a window where + * we'll miss signals until we manage to re-create the pipe. + * However, just trying to use the same pipe again is probably not + * going to work, so we have little choice. + */ + CloseHandle(pipe); + pipe = INVALID_HANDLE_VALUE; + } + } + return 0; +} + + +/* Console control handler will execute on a thread created + by the OS at the time of invocation */ +static BOOL WINAPI +pg_console_handler(DWORD dwCtrlType) +{ + if (dwCtrlType == CTRL_C_EVENT || + dwCtrlType == CTRL_BREAK_EVENT || + dwCtrlType == CTRL_CLOSE_EVENT || + dwCtrlType == CTRL_SHUTDOWN_EVENT) + { + pg_queue_signal(SIGINT); + return TRUE; + } + return FALSE; +} diff --git a/contrib/libs/postgresql/src/backend/port/win32/socket.c b/contrib/libs/postgresql/src/backend/port/win32/socket.c new file mode 100644 index 0000000000..af151e8470 --- /dev/null +++ b/contrib/libs/postgresql/src/backend/port/win32/socket.c @@ -0,0 +1,700 @@ +/*------------------------------------------------------------------------- + * + * socket.c + * Microsoft Windows Win32 Socket Functions + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/port/win32/socket.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +/* + * Indicate if pgwin32_recv() and pgwin32_send() should operate + * in non-blocking mode. + * + * Since the socket emulation layer always sets the actual socket to + * non-blocking mode in order to be able to deliver signals, we must + * specify this in a separate flag if we actually need non-blocking + * operation. + * + * This flag changes the behaviour *globally* for all socket operations, + * so it should only be set for very short periods of time. + */ +int pgwin32_noblock = 0; + +/* Undef the macros defined in win32.h, so we can access system functions */ +#undef socket +#undef bind +#undef listen +#undef accept +#undef connect +#undef select +#undef recv +#undef send + +/* + * Blocking socket functions implemented so they listen on both + * the socket and the signal event, required for signal handling. + */ + +/* + * Convert the last socket error code into errno + * + * Note: where there is a direct correspondence between a WSAxxx error code + * and a Berkeley error symbol, this mapping is actually a no-op, because + * in win32.h we redefine the network-related Berkeley error symbols to have + * the values of their WSAxxx counterparts. The point of the switch is + * mostly to translate near-miss error codes into something that's sensible + * in the Berkeley universe. + */ +static void +TranslateSocketError(void) +{ + switch (WSAGetLastError()) + { + case WSAEINVAL: + case WSANOTINITIALISED: + case WSAEINVALIDPROVIDER: + case WSAEINVALIDPROCTABLE: + case WSAEDESTADDRREQ: + errno = EINVAL; + break; + case WSAEINPROGRESS: + errno = EINPROGRESS; + break; + case WSAEFAULT: + errno = EFAULT; + break; + case WSAEISCONN: + errno = EISCONN; + break; + case WSAEMSGSIZE: + errno = EMSGSIZE; + break; + case WSAEAFNOSUPPORT: + errno = EAFNOSUPPORT; + break; + case WSAEMFILE: + errno = EMFILE; + break; + case WSAENOBUFS: + errno = ENOBUFS; + break; + case WSAEPROTONOSUPPORT: + case WSAEPROTOTYPE: + case WSAESOCKTNOSUPPORT: + errno = EPROTONOSUPPORT; + break; + case WSAECONNABORTED: + errno = ECONNABORTED; + break; + case WSAECONNREFUSED: + errno = ECONNREFUSED; + break; + case WSAECONNRESET: + errno = ECONNRESET; + break; + case WSAEINTR: + errno = EINTR; + break; + case WSAENOTSOCK: + errno = ENOTSOCK; + break; + case WSAEOPNOTSUPP: + errno = EOPNOTSUPP; + break; + case WSAEWOULDBLOCK: + errno = EWOULDBLOCK; + break; + case WSAEACCES: + errno = EACCES; + break; + case WSAEADDRINUSE: + errno = EADDRINUSE; + break; + case WSAEADDRNOTAVAIL: + errno = EADDRNOTAVAIL; + break; + case WSAEHOSTDOWN: + errno = EHOSTDOWN; + break; + case WSAEHOSTUNREACH: + case WSAHOST_NOT_FOUND: + errno = EHOSTUNREACH; + break; + case WSAENETDOWN: + errno = ENETDOWN; + break; + case WSAENETUNREACH: + errno = ENETUNREACH; + break; + case WSAENETRESET: + errno = ENETRESET; + break; + case WSAENOTCONN: + case WSAESHUTDOWN: + case WSAEDISCON: + errno = ENOTCONN; + break; + default: + ereport(NOTICE, + (errmsg_internal("unrecognized win32 socket error code: %d", WSAGetLastError()))); + errno = EINVAL; + } +} + +static int +pgwin32_poll_signals(void) +{ + if (UNBLOCKED_SIGNAL_QUEUE()) + { + pgwin32_dispatch_queued_signals(); + errno = EINTR; + return 1; + } + return 0; +} + +static int +isDataGram(SOCKET s) +{ + int type; + int typelen = sizeof(type); + + if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &type, &typelen)) + return 1; + + return (type == SOCK_DGRAM) ? 1 : 0; +} + +int +pgwin32_waitforsinglesocket(SOCKET s, int what, int timeout) +{ + static HANDLE waitevent = INVALID_HANDLE_VALUE; + static SOCKET current_socket = INVALID_SOCKET; + static int isUDP = 0; + HANDLE events[2]; + int r; + + /* Create an event object just once and use it on all future calls */ + if (waitevent == INVALID_HANDLE_VALUE) + { + waitevent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (waitevent == INVALID_HANDLE_VALUE) + ereport(ERROR, + (errmsg_internal("could not create socket waiting event: error code %lu", GetLastError()))); + } + else if (!ResetEvent(waitevent)) + ereport(ERROR, + (errmsg_internal("could not reset socket waiting event: error code %lu", GetLastError()))); + + /* + * Track whether socket is UDP or not. (NB: most likely, this is both + * useless and wrong; there is no reason to think that the behavior of + * WSAEventSelect is different for TCP and UDP.) + */ + if (current_socket != s) + isUDP = isDataGram(s); + current_socket = s; + + /* + * Attach event to socket. NOTE: we must detach it again before + * returning, since other bits of code may try to attach other events to + * the socket. + */ + if (WSAEventSelect(s, waitevent, what) != 0) + { + TranslateSocketError(); + return 0; + } + + events[0] = pgwin32_signal_event; + events[1] = waitevent; + + /* + * Just a workaround of unknown locking problem with writing in UDP socket + * under high load: Client's pgsql backend sleeps infinitely in + * WaitForMultipleObjectsEx, pgstat process sleeps in pgwin32_select(). + * So, we will wait with small timeout(0.1 sec) and if socket is still + * blocked, try WSASend (see comments in pgwin32_select) and wait again. + */ + if ((what & FD_WRITE) && isUDP) + { + for (;;) + { + r = WaitForMultipleObjectsEx(2, events, FALSE, 100, TRUE); + + if (r == WAIT_TIMEOUT) + { + char c; + WSABUF buf; + DWORD sent; + + buf.buf = &c; + buf.len = 0; + + r = WSASend(s, &buf, 1, &sent, 0, NULL, NULL); + if (r == 0) /* Completed - means things are fine! */ + { + WSAEventSelect(s, NULL, 0); + return 1; + } + else if (WSAGetLastError() != WSAEWOULDBLOCK) + { + TranslateSocketError(); + WSAEventSelect(s, NULL, 0); + return 0; + } + } + else + break; + } + } + else + r = WaitForMultipleObjectsEx(2, events, FALSE, timeout, TRUE); + + WSAEventSelect(s, NULL, 0); + + if (r == WAIT_OBJECT_0 || r == WAIT_IO_COMPLETION) + { + pgwin32_dispatch_queued_signals(); + errno = EINTR; + return 0; + } + if (r == WAIT_OBJECT_0 + 1) + return 1; + if (r == WAIT_TIMEOUT) + { + errno = EWOULDBLOCK; + return 0; + } + ereport(ERROR, + (errmsg_internal("unrecognized return value from WaitForMultipleObjects: %d (error code %lu)", r, GetLastError()))); + return 0; +} + +/* + * Create a socket, setting it to overlapped and non-blocking + */ +SOCKET +pgwin32_socket(int af, int type, int protocol) +{ + SOCKET s; + unsigned long on = 1; + + s = WSASocket(af, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED); + if (s == INVALID_SOCKET) + { + TranslateSocketError(); + return INVALID_SOCKET; + } + + if (ioctlsocket(s, FIONBIO, &on)) + { + TranslateSocketError(); + return INVALID_SOCKET; + } + errno = 0; + + return s; +} + +int +pgwin32_bind(SOCKET s, struct sockaddr *addr, int addrlen) +{ + int res; + + res = bind(s, addr, addrlen); + if (res < 0) + TranslateSocketError(); + return res; +} + +int +pgwin32_listen(SOCKET s, int backlog) +{ + int res; + + res = listen(s, backlog); + if (res < 0) + TranslateSocketError(); + return res; +} + +SOCKET +pgwin32_accept(SOCKET s, struct sockaddr *addr, int *addrlen) +{ + SOCKET rs; + + /* + * Poll for signals, but don't return with EINTR, since we don't handle + * that in pqcomm.c + */ + pgwin32_poll_signals(); + + rs = WSAAccept(s, addr, addrlen, NULL, 0); + if (rs == INVALID_SOCKET) + { + TranslateSocketError(); + return INVALID_SOCKET; + } + return rs; +} + + +/* No signal delivery during connect. */ +int +pgwin32_connect(SOCKET s, const struct sockaddr *addr, int addrlen) +{ + int r; + + r = WSAConnect(s, addr, addrlen, NULL, NULL, NULL, NULL); + if (r == 0) + return 0; + + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + TranslateSocketError(); + return -1; + } + + while (pgwin32_waitforsinglesocket(s, FD_CONNECT, INFINITE) == 0) + { + /* Loop endlessly as long as we are just delivering signals */ + } + + return 0; +} + +int +pgwin32_recv(SOCKET s, char *buf, int len, int f) +{ + WSABUF wbuf; + int r; + DWORD b; + DWORD flags = f; + int n; + + if (pgwin32_poll_signals()) + return -1; + + wbuf.len = len; + wbuf.buf = buf; + + r = WSARecv(s, &wbuf, 1, &b, &flags, NULL, NULL); + if (r != SOCKET_ERROR) + return b; /* success */ + + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + TranslateSocketError(); + return -1; + } + + if (pgwin32_noblock) + { + /* + * No data received, and we are in "emulated non-blocking mode", so + * return indicating that we'd block if we were to continue. + */ + errno = EWOULDBLOCK; + return -1; + } + + /* We're in blocking mode, so wait for data */ + + for (n = 0; n < 5; n++) + { + if (pgwin32_waitforsinglesocket(s, FD_READ | FD_CLOSE | FD_ACCEPT, + INFINITE) == 0) + return -1; /* errno already set */ + + r = WSARecv(s, &wbuf, 1, &b, &flags, NULL, NULL); + if (r != SOCKET_ERROR) + return b; /* success */ + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + TranslateSocketError(); + return -1; + } + + /* + * There seem to be cases on win2k (at least) where WSARecv can return + * WSAEWOULDBLOCK even when pgwin32_waitforsinglesocket claims the + * socket is readable. In this case, just sleep for a moment and try + * again. We try up to 5 times - if it fails more than that it's not + * likely to ever come back. + */ + pg_usleep(10000); + } + ereport(NOTICE, + (errmsg_internal("could not read from ready socket (after retries)"))); + errno = EWOULDBLOCK; + return -1; +} + +/* + * The second argument to send() is defined by SUS to be a "const void *" + * and so we use the same signature here to keep compilers happy when + * handling callers. + * + * But the buf member of a WSABUF struct is defined as "char *", so we cast + * the second argument to that here when assigning it, also to keep compilers + * happy. + */ + +int +pgwin32_send(SOCKET s, const void *buf, int len, int flags) +{ + WSABUF wbuf; + int r; + DWORD b; + + if (pgwin32_poll_signals()) + return -1; + + wbuf.len = len; + wbuf.buf = (char *) buf; + + /* + * Readiness of socket to send data to UDP socket may be not true: socket + * can become busy again! So loop until send or error occurs. + */ + for (;;) + { + r = WSASend(s, &wbuf, 1, &b, flags, NULL, NULL); + if (r != SOCKET_ERROR && b > 0) + /* Write succeeded right away */ + return b; + + if (r == SOCKET_ERROR && + WSAGetLastError() != WSAEWOULDBLOCK) + { + TranslateSocketError(); + return -1; + } + + if (pgwin32_noblock) + { + /* + * No data sent, and we are in "emulated non-blocking mode", so + * return indicating that we'd block if we were to continue. + */ + errno = EWOULDBLOCK; + return -1; + } + + /* No error, zero bytes (win2000+) or error+WSAEWOULDBLOCK (<=nt4) */ + + if (pgwin32_waitforsinglesocket(s, FD_WRITE | FD_CLOSE, INFINITE) == 0) + return -1; + } + + return -1; +} + + +/* + * Wait for activity on one or more sockets. + * While waiting, allow signals to run + * + * NOTE! Currently does not implement exceptfds check, + * since it is not used in postgresql! + */ +int +pgwin32_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout) +{ + WSAEVENT events[FD_SETSIZE * 2]; /* worst case is readfds totally + * different from writefds, so + * 2*FD_SETSIZE sockets */ + SOCKET sockets[FD_SETSIZE * 2]; + int numevents = 0; + int i; + int r; + DWORD timeoutval = WSA_INFINITE; + FD_SET outreadfds; + FD_SET outwritefds; + int nummatches = 0; + + Assert(exceptfds == NULL); + + if (pgwin32_poll_signals()) + return -1; + + FD_ZERO(&outreadfds); + FD_ZERO(&outwritefds); + + /* + * Windows does not guarantee to log an FD_WRITE network event indicating + * that more data can be sent unless the previous send() failed with + * WSAEWOULDBLOCK. While our caller might well have made such a call, we + * cannot assume that here. Therefore, if waiting for write-ready, force + * the issue by doing a dummy send(). If the dummy send() succeeds, + * assume that the socket is in fact write-ready, and return immediately. + * Also, if it fails with something other than WSAEWOULDBLOCK, return a + * write-ready indication to let our caller deal with the error condition. + */ + if (writefds != NULL) + { + for (i = 0; i < writefds->fd_count; i++) + { + char c; + WSABUF buf; + DWORD sent; + + buf.buf = &c; + buf.len = 0; + + r = WSASend(writefds->fd_array[i], &buf, 1, &sent, 0, NULL, NULL); + if (r == 0 || WSAGetLastError() != WSAEWOULDBLOCK) + FD_SET(writefds->fd_array[i], &outwritefds); + } + + /* If we found any write-ready sockets, just return them immediately */ + if (outwritefds.fd_count > 0) + { + memcpy(writefds, &outwritefds, sizeof(fd_set)); + if (readfds) + FD_ZERO(readfds); + return outwritefds.fd_count; + } + } + + + /* Now set up for an actual select */ + + if (timeout != NULL) + { + /* timeoutval is in milliseconds */ + timeoutval = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; + } + + if (readfds != NULL) + { + for (i = 0; i < readfds->fd_count; i++) + { + events[numevents] = WSACreateEvent(); + sockets[numevents] = readfds->fd_array[i]; + numevents++; + } + } + if (writefds != NULL) + { + for (i = 0; i < writefds->fd_count; i++) + { + if (!readfds || + !FD_ISSET(writefds->fd_array[i], readfds)) + { + /* If the socket is not in the read list */ + events[numevents] = WSACreateEvent(); + sockets[numevents] = writefds->fd_array[i]; + numevents++; + } + } + } + + for (i = 0; i < numevents; i++) + { + int flags = 0; + + if (readfds && FD_ISSET(sockets[i], readfds)) + flags |= FD_READ | FD_ACCEPT | FD_CLOSE; + + if (writefds && FD_ISSET(sockets[i], writefds)) + flags |= FD_WRITE | FD_CLOSE; + + if (WSAEventSelect(sockets[i], events[i], flags) != 0) + { + TranslateSocketError(); + /* release already-assigned event objects */ + while (--i >= 0) + WSAEventSelect(sockets[i], NULL, 0); + for (i = 0; i < numevents; i++) + WSACloseEvent(events[i]); + return -1; + } + } + + events[numevents] = pgwin32_signal_event; + r = WaitForMultipleObjectsEx(numevents + 1, events, FALSE, timeoutval, TRUE); + if (r != WAIT_TIMEOUT && r != WAIT_IO_COMPLETION && r != (WAIT_OBJECT_0 + numevents)) + { + /* + * We scan all events, even those not signaled, in case more than one + * event has been tagged but Wait.. can only return one. + */ + WSANETWORKEVENTS resEvents; + + for (i = 0; i < numevents; i++) + { + ZeroMemory(&resEvents, sizeof(resEvents)); + if (WSAEnumNetworkEvents(sockets[i], events[i], &resEvents) != 0) + elog(ERROR, "failed to enumerate network events: error code %d", + WSAGetLastError()); + /* Read activity? */ + if (readfds && FD_ISSET(sockets[i], readfds)) + { + if ((resEvents.lNetworkEvents & FD_READ) || + (resEvents.lNetworkEvents & FD_ACCEPT) || + (resEvents.lNetworkEvents & FD_CLOSE)) + { + FD_SET(sockets[i], &outreadfds); + + nummatches++; + } + } + /* Write activity? */ + if (writefds && FD_ISSET(sockets[i], writefds)) + { + if ((resEvents.lNetworkEvents & FD_WRITE) || + (resEvents.lNetworkEvents & FD_CLOSE)) + { + FD_SET(sockets[i], &outwritefds); + + nummatches++; + } + } + } + } + + /* Clean up all the event objects */ + for (i = 0; i < numevents; i++) + { + WSAEventSelect(sockets[i], NULL, 0); + WSACloseEvent(events[i]); + } + + if (r == WSA_WAIT_TIMEOUT) + { + if (readfds) + FD_ZERO(readfds); + if (writefds) + FD_ZERO(writefds); + return 0; + } + + /* Signal-like events. */ + if (r == WAIT_OBJECT_0 + numevents || r == WAIT_IO_COMPLETION) + { + pgwin32_dispatch_queued_signals(); + errno = EINTR; + if (readfds) + FD_ZERO(readfds); + if (writefds) + FD_ZERO(writefds); + return -1; + } + + /* Overwrite socket sets with our resulting values */ + if (readfds) + memcpy(readfds, &outreadfds, sizeof(fd_set)); + if (writefds) + memcpy(writefds, &outwritefds, sizeof(fd_set)); + return nummatches; +} diff --git a/contrib/libs/postgresql/src/backend/port/win32/timer.c b/contrib/libs/postgresql/src/backend/port/win32/timer.c new file mode 100644 index 0000000000..53fdae9468 --- /dev/null +++ b/contrib/libs/postgresql/src/backend/port/win32/timer.c @@ -0,0 +1,121 @@ +/*------------------------------------------------------------------------- + * + * timer.c + * Microsoft Windows Win32 Timer Implementation + * + * Limitations of this implementation: + * + * - Does not support interval timer (value->it_interval) + * - Only supports ITIMER_REAL + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/port/win32/timer.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + + +/* Communication area for inter-thread communication */ +typedef struct timerCA +{ + struct itimerval value; + HANDLE event; + CRITICAL_SECTION crit_sec; +} timerCA; + +static timerCA timerCommArea; +static HANDLE timerThreadHandle = INVALID_HANDLE_VALUE; + + +/* Timer management thread */ +static DWORD WINAPI +pg_timer_thread(LPVOID param) +{ + DWORD waittime; + + Assert(param == NULL); + + waittime = INFINITE; + + for (;;) + { + int r; + + r = WaitForSingleObjectEx(timerCommArea.event, waittime, FALSE); + if (r == WAIT_OBJECT_0) + { + /* Event signaled from main thread, change the timer */ + EnterCriticalSection(&timerCommArea.crit_sec); + if (timerCommArea.value.it_value.tv_sec == 0 && + timerCommArea.value.it_value.tv_usec == 0) + waittime = INFINITE; /* Cancel the interrupt */ + else + { + /* WaitForSingleObjectEx() uses milliseconds, round up */ + waittime = (timerCommArea.value.it_value.tv_usec + 999) / 1000 + + timerCommArea.value.it_value.tv_sec * 1000; + } + ResetEvent(timerCommArea.event); + LeaveCriticalSection(&timerCommArea.crit_sec); + } + else if (r == WAIT_TIMEOUT) + { + /* Timeout expired, signal SIGALRM and turn it off */ + pg_queue_signal(SIGALRM); + waittime = INFINITE; + } + else + { + /* Should never happen */ + Assert(false); + } + } + + return 0; +} + +/* + * Win32 setitimer emulation by creating a persistent thread + * to handle the timer setting and notification upon timeout. + */ +int +setitimer(int which, const struct itimerval *value, struct itimerval *ovalue) +{ + Assert(value != NULL); + Assert(value->it_interval.tv_sec == 0 && value->it_interval.tv_usec == 0); + Assert(which == ITIMER_REAL); + + if (timerThreadHandle == INVALID_HANDLE_VALUE) + { + /* First call in this backend, create event and the timer thread */ + timerCommArea.event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (timerCommArea.event == NULL) + ereport(FATAL, + (errmsg_internal("could not create timer event: error code %lu", + GetLastError()))); + + MemSet(&timerCommArea.value, 0, sizeof(struct itimerval)); + + InitializeCriticalSection(&timerCommArea.crit_sec); + + timerThreadHandle = CreateThread(NULL, 0, pg_timer_thread, NULL, 0, NULL); + if (timerThreadHandle == INVALID_HANDLE_VALUE) + ereport(FATAL, + (errmsg_internal("could not create timer thread: error code %lu", + GetLastError()))); + } + + /* Request the timer thread to change settings */ + EnterCriticalSection(&timerCommArea.crit_sec); + if (ovalue) + *ovalue = timerCommArea.value; + timerCommArea.value = *value; + LeaveCriticalSection(&timerCommArea.crit_sec); + SetEvent(timerCommArea.event); + + return 0; +} diff --git a/contrib/libs/postgresql/src/backend/port/win32_sema.c b/contrib/libs/postgresql/src/backend/port/win32_sema.c new file mode 100644 index 0000000000..858b88adae --- /dev/null +++ b/contrib/libs/postgresql/src/backend/port/win32_sema.c @@ -0,0 +1,235 @@ +/*------------------------------------------------------------------------- + * + * win32_sema.c + * Microsoft Windows Win32 Semaphores Emulation + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/port/win32_sema.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" +#include "storage/ipc.h" +#include "storage/pg_sema.h" + +static HANDLE *mySemSet; /* IDs of sema sets acquired so far */ +static int numSems; /* number of sema sets acquired so far */ +static int maxSems; /* allocated size of mySemaSet array */ + +static void ReleaseSemaphores(int code, Datum arg); + + +/* + * Report amount of shared memory needed for semaphores + */ +Size +PGSemaphoreShmemSize(int maxSemas) +{ + /* No shared memory needed on Windows */ + return 0; +} + +/* + * PGReserveSemaphores --- initialize semaphore support + * + * In the Win32 implementation, we acquire semaphores on-demand; the + * maxSemas parameter is just used to size the array that keeps track of + * acquired semas for subsequent releasing. We use anonymous semaphores + * so the semaphores are automatically freed when the last referencing + * process exits. + */ +void +PGReserveSemaphores(int maxSemas) +{ + mySemSet = (HANDLE *) malloc(maxSemas * sizeof(HANDLE)); + if (mySemSet == NULL) + elog(PANIC, "out of memory"); + numSems = 0; + maxSems = maxSemas; + + on_shmem_exit(ReleaseSemaphores, 0); +} + +/* + * Release semaphores at shutdown or shmem reinitialization + * + * (called as an on_shmem_exit callback, hence funny argument list) + */ +static void +ReleaseSemaphores(int code, Datum arg) +{ + int i; + + for (i = 0; i < numSems; i++) + CloseHandle(mySemSet[i]); + free(mySemSet); +} + +/* + * PGSemaphoreCreate + * + * Allocate a PGSemaphore structure with initial count 1 + */ +PGSemaphore +PGSemaphoreCreate(void) +{ + HANDLE cur_handle; + SECURITY_ATTRIBUTES sec_attrs; + + /* Can't do this in a backend, because static state is postmaster's */ + Assert(!IsUnderPostmaster); + + if (numSems >= maxSems) + elog(PANIC, "too many semaphores created"); + + ZeroMemory(&sec_attrs, sizeof(sec_attrs)); + sec_attrs.nLength = sizeof(sec_attrs); + sec_attrs.lpSecurityDescriptor = NULL; + sec_attrs.bInheritHandle = TRUE; + + /* We don't need a named semaphore */ + cur_handle = CreateSemaphore(&sec_attrs, 1, 32767, NULL); + if (cur_handle) + { + /* Successfully done */ + mySemSet[numSems++] = cur_handle; + } + else + ereport(PANIC, + (errmsg("could not create semaphore: error code %lu", + GetLastError()))); + + return (PGSemaphore) cur_handle; +} + +/* + * PGSemaphoreReset + * + * Reset a previously-initialized PGSemaphore to have count 0 + */ +void +PGSemaphoreReset(PGSemaphore sema) +{ + /* + * There's no direct API for this in Win32, so we have to ratchet the + * semaphore down to 0 with repeated trylock's. + */ + while (PGSemaphoreTryLock(sema)) + /* loop */ ; +} + +/* + * PGSemaphoreLock + * + * Lock a semaphore (decrement count), blocking if count would be < 0. + */ +void +PGSemaphoreLock(PGSemaphore sema) +{ + HANDLE wh[2]; + bool done = false; + + /* + * Note: pgwin32_signal_event should be first to ensure that it will be + * reported when multiple events are set. We want to guarantee that + * pending signals are serviced. + */ + wh[0] = pgwin32_signal_event; + wh[1] = sema; + + /* + * As in other implementations of PGSemaphoreLock, we need to check for + * cancel/die interrupts each time through the loop. But here, there is + * no hidden magic about whether the syscall will internally service a + * signal --- we do that ourselves. + */ + while (!done) + { + DWORD rc; + + CHECK_FOR_INTERRUPTS(); + + rc = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE); + switch (rc) + { + case WAIT_OBJECT_0: + /* Signal event is set - we have a signal to deliver */ + pgwin32_dispatch_queued_signals(); + break; + case WAIT_OBJECT_0 + 1: + /* We got it! */ + done = true; + break; + case WAIT_IO_COMPLETION: + + /* + * The system interrupted the wait to execute an I/O + * completion routine or asynchronous procedure call in this + * thread. PostgreSQL does not provoke either of these, but + * atypical loaded DLLs or even other processes might do so. + * Now, resume waiting. + */ + break; + case WAIT_FAILED: + ereport(FATAL, + (errmsg("could not lock semaphore: error code %lu", + GetLastError()))); + break; + default: + elog(FATAL, "unexpected return code from WaitForMultipleObjectsEx(): %lu", rc); + break; + } + } +} + +/* + * PGSemaphoreUnlock + * + * Unlock a semaphore (increment count) + */ +void +PGSemaphoreUnlock(PGSemaphore sema) +{ + if (!ReleaseSemaphore(sema, 1, NULL)) + ereport(FATAL, + (errmsg("could not unlock semaphore: error code %lu", + GetLastError()))); +} + +/* + * PGSemaphoreTryLock + * + * Lock a semaphore only if able to do so without blocking + */ +bool +PGSemaphoreTryLock(PGSemaphore sema) +{ + DWORD ret; + + ret = WaitForSingleObject(sema, 0); + + if (ret == WAIT_OBJECT_0) + { + /* We got it! */ + return true; + } + else if (ret == WAIT_TIMEOUT) + { + /* Can't get it */ + errno = EAGAIN; + return false; + } + + /* Otherwise we are in trouble */ + ereport(FATAL, + (errmsg("could not try-lock semaphore: error code %lu", + GetLastError()))); + + /* keep compiler quiet */ + return false; +} diff --git a/contrib/libs/postgresql/src/backend/port/win32_shmem.c b/contrib/libs/postgresql/src/backend/port/win32_shmem.c new file mode 100644 index 0000000000..d7a71992d8 --- /dev/null +++ b/contrib/libs/postgresql/src/backend/port/win32_shmem.c @@ -0,0 +1,607 @@ +/*------------------------------------------------------------------------- + * + * win32_shmem.c + * Implement shared memory using win32 facilities + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/port/win32_shmem.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "miscadmin.h" +#include "storage/dsm.h" +#include "storage/ipc.h" +#include "storage/pg_shmem.h" + +/* + * Early in a process's life, Windows asynchronously creates threads for the + * process's "default thread pool" + * (https://docs.microsoft.com/en-us/windows/desktop/ProcThread/thread-pools). + * Occasionally, thread creation allocates a stack after + * PGSharedMemoryReAttach() has released UsedShmemSegAddr and before it has + * mapped shared memory at UsedShmemSegAddr. This would cause mapping to fail + * if the allocator preferred the just-released region for allocating the new + * thread stack. We observed such failures in some Windows Server 2016 + * configurations. To give the system another region to prefer, reserve and + * release an additional, protective region immediately before reserving or + * releasing shared memory. The idea is that, if the allocator handed out + * REGION1 pages before REGION2 pages at one occasion, it will do so whenever + * both regions are free. Windows Server 2016 exhibits that behavior, and a + * system behaving differently would have less need to protect + * UsedShmemSegAddr. The protective region must be at least large enough for + * one thread stack. However, ten times as much is less than 2% of the 32-bit + * address space and is negligible relative to the 64-bit address space. + */ +#define PROTECTIVE_REGION_SIZE (10 * WIN32_STACK_RLIMIT) +void *ShmemProtectiveRegion = NULL; + +HANDLE UsedShmemSegID = INVALID_HANDLE_VALUE; +void *UsedShmemSegAddr = NULL; +static Size UsedShmemSegSize = 0; + +static bool EnableLockPagesPrivilege(int elevel); +static void pgwin32_SharedMemoryDelete(int status, Datum shmId); + +/* + * Generate shared memory segment name. Expand the data directory, to generate + * an identifier unique for this data directory. Then replace all backslashes + * with forward slashes, since backslashes aren't permitted in global object names. + * + * Store the shared memory segment in the Global\ namespace (requires NT2 TSE or + * 2000, but that's all we support for other reasons as well), to make sure you can't + * open two postmasters in different sessions against the same data directory. + * + * XXX: What happens with junctions? It's only someone breaking things on purpose, + * and this is still better than before, but we might want to do something about + * that sometime in the future. + */ +static char * +GetSharedMemName(void) +{ + char *retptr; + DWORD bufsize; + DWORD r; + char *cp; + + bufsize = GetFullPathName(DataDir, 0, NULL, NULL); + if (bufsize == 0) + elog(FATAL, "could not get size for full pathname of datadir %s: error code %lu", + DataDir, GetLastError()); + + retptr = malloc(bufsize + 18); /* 18 for Global\PostgreSQL: */ + if (retptr == NULL) + elog(FATAL, "could not allocate memory for shared memory name"); + + strcpy(retptr, "Global\\PostgreSQL:"); + r = GetFullPathName(DataDir, bufsize, retptr + 18, NULL); + if (r == 0 || r > bufsize) + elog(FATAL, "could not generate full pathname for datadir %s: error code %lu", + DataDir, GetLastError()); + + /* + * XXX: Intentionally overwriting the Global\ part here. This was not the + * original approach, but putting it in the actual Global\ namespace + * causes permission errors in a lot of cases, so we leave it in the + * default namespace for now. + */ + for (cp = retptr; *cp; cp++) + if (*cp == '\\') + *cp = '/'; + + return retptr; +} + + +/* + * PGSharedMemoryIsInUse + * + * Is a previously-existing shmem segment still existing and in use? + * + * The point of this exercise is to detect the case where a prior postmaster + * crashed, but it left child backends that are still running. Therefore + * we only care about shmem segments that are associated with the intended + * DataDir. This is an important consideration since accidental matches of + * shmem segment IDs are reasonably common. + */ +bool +PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) +{ + char *szShareMem; + HANDLE hmap; + + szShareMem = GetSharedMemName(); + + hmap = OpenFileMapping(FILE_MAP_READ, FALSE, szShareMem); + + free(szShareMem); + + if (hmap == NULL) + return false; + + CloseHandle(hmap); + return true; +} + +/* + * EnableLockPagesPrivilege + * + * Try to acquire SeLockMemoryPrivilege so we can use large pages. + */ +static bool +EnableLockPagesPrivilege(int elevel) +{ + HANDLE hToken; + TOKEN_PRIVILEGES tp; + LUID luid; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + ereport(elevel, + (errmsg("could not enable user right \"%s\": error code %lu", + + /* + * translator: This is a term from Windows and should be translated to + * match the Windows localization. + */ + _("Lock pages in memory"), + GetLastError()), + errdetail("Failed system call was %s.", "OpenProcessToken"))); + return FALSE; + } + + if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid)) + { + ereport(elevel, + (errmsg("could not enable user right \"%s\": error code %lu", _("Lock pages in memory"), GetLastError()), + errdetail("Failed system call was %s.", "LookupPrivilegeValue"))); + CloseHandle(hToken); + return FALSE; + } + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL)) + { + ereport(elevel, + (errmsg("could not enable user right \"%s\": error code %lu", _("Lock pages in memory"), GetLastError()), + errdetail("Failed system call was %s.", "AdjustTokenPrivileges"))); + CloseHandle(hToken); + return FALSE; + } + + if (GetLastError() != ERROR_SUCCESS) + { + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) + ereport(elevel, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("could not enable user right \"%s\"", _("Lock pages in memory")), + errhint("Assign user right \"%s\" to the Windows user account which runs PostgreSQL.", + _("Lock pages in memory")))); + else + ereport(elevel, + (errmsg("could not enable user right \"%s\": error code %lu", _("Lock pages in memory"), GetLastError()), + errdetail("Failed system call was %s.", "AdjustTokenPrivileges"))); + CloseHandle(hToken); + return FALSE; + } + + CloseHandle(hToken); + + return TRUE; +} + +/* + * PGSharedMemoryCreate + * + * Create a shared memory segment of the given size and initialize its + * standard header. + */ +PGShmemHeader * +PGSharedMemoryCreate(Size size, + PGShmemHeader **shim) +{ + void *memAddress; + PGShmemHeader *hdr; + HANDLE hmap, + hmap2; + char *szShareMem; + int i; + DWORD size_high; + DWORD size_low; + SIZE_T largePageSize = 0; + Size orig_size = size; + DWORD flProtect = PAGE_READWRITE; + + ShmemProtectiveRegion = VirtualAlloc(NULL, PROTECTIVE_REGION_SIZE, + MEM_RESERVE, PAGE_NOACCESS); + if (ShmemProtectiveRegion == NULL) + elog(FATAL, "could not reserve memory region: error code %lu", + GetLastError()); + + /* Room for a header? */ + Assert(size > MAXALIGN(sizeof(PGShmemHeader))); + + szShareMem = GetSharedMemName(); + + UsedShmemSegAddr = NULL; + + if (huge_pages == HUGE_PAGES_ON || huge_pages == HUGE_PAGES_TRY) + { + /* Does the processor support large pages? */ + largePageSize = GetLargePageMinimum(); + if (largePageSize == 0) + { + ereport(huge_pages == HUGE_PAGES_ON ? FATAL : DEBUG1, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("the processor does not support large pages"))); + ereport(DEBUG1, + (errmsg_internal("disabling huge pages"))); + } + else if (!EnableLockPagesPrivilege(huge_pages == HUGE_PAGES_ON ? FATAL : DEBUG1)) + { + ereport(DEBUG1, + (errmsg_internal("disabling huge pages"))); + } + else + { + /* Huge pages available and privilege enabled, so turn on */ + flProtect = PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES; + + /* Round size up as appropriate. */ + if (size % largePageSize != 0) + size += largePageSize - (size % largePageSize); + } + } + +retry: +#ifdef _WIN64 + size_high = size >> 32; +#else + size_high = 0; +#endif + size_low = (DWORD) size; + + /* + * When recycling a shared memory segment, it may take a short while + * before it gets dropped from the global namespace. So re-try after + * sleeping for a second, and continue retrying 10 times. (both the 1 + * second time and the 10 retries are completely arbitrary) + */ + for (i = 0; i < 10; i++) + { + /* + * In case CreateFileMapping() doesn't set the error code to 0 on + * success + */ + SetLastError(0); + + hmap = CreateFileMapping(INVALID_HANDLE_VALUE, /* Use the pagefile */ + NULL, /* Default security attrs */ + flProtect, + size_high, /* Size Upper 32 Bits */ + size_low, /* Size Lower 32 bits */ + szShareMem); + + if (!hmap) + { + if (GetLastError() == ERROR_NO_SYSTEM_RESOURCES && + huge_pages == HUGE_PAGES_TRY && + (flProtect & SEC_LARGE_PAGES) != 0) + { + elog(DEBUG1, "CreateFileMapping(%zu) with SEC_LARGE_PAGES failed, " + "huge pages disabled", + size); + + /* + * Use the original size, not the rounded-up value, when + * falling back to non-huge pages. + */ + size = orig_size; + flProtect = PAGE_READWRITE; + goto retry; + } + else + ereport(FATAL, + (errmsg("could not create shared memory segment: error code %lu", GetLastError()), + errdetail("Failed system call was CreateFileMapping(size=%zu, name=%s).", + size, szShareMem))); + } + + /* + * If the segment already existed, CreateFileMapping() will return a + * handle to the existing one and set ERROR_ALREADY_EXISTS. + */ + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + CloseHandle(hmap); /* Close the handle, since we got a valid one + * to the previous segment. */ + hmap = NULL; + Sleep(1000); + continue; + } + break; + } + + /* + * If the last call in the loop still returned ERROR_ALREADY_EXISTS, this + * shared memory segment exists and we assume it belongs to somebody else. + */ + if (!hmap) + ereport(FATAL, + (errmsg("pre-existing shared memory block is still in use"), + errhint("Check if there are any old server processes still running, and terminate them."))); + + free(szShareMem); + + /* + * Make the handle inheritable + */ + if (!DuplicateHandle(GetCurrentProcess(), hmap, GetCurrentProcess(), &hmap2, 0, TRUE, DUPLICATE_SAME_ACCESS)) + ereport(FATAL, + (errmsg("could not create shared memory segment: error code %lu", GetLastError()), + errdetail("Failed system call was DuplicateHandle."))); + + /* + * Close the old, non-inheritable handle. If this fails we don't really + * care. + */ + if (!CloseHandle(hmap)) + elog(LOG, "could not close handle to shared memory: error code %lu", GetLastError()); + + + /* + * Get a pointer to the new shared memory segment. Map the whole segment + * at once, and let the system decide on the initial address. + */ + memAddress = MapViewOfFileEx(hmap2, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0, NULL); + if (!memAddress) + ereport(FATAL, + (errmsg("could not create shared memory segment: error code %lu", GetLastError()), + errdetail("Failed system call was MapViewOfFileEx."))); + + + + /* + * OK, we created a new segment. Mark it as created by this process. The + * order of assignments here is critical so that another Postgres process + * can't see the header as valid but belonging to an invalid PID! + */ + hdr = (PGShmemHeader *) memAddress; + hdr->creatorPID = getpid(); + hdr->magic = PGShmemMagic; + + /* + * Initialize space allocation status for segment. + */ + hdr->totalsize = size; + hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader)); + hdr->dsm_control = 0; + + /* Save info for possible future use */ + UsedShmemSegAddr = memAddress; + UsedShmemSegSize = size; + UsedShmemSegID = hmap2; + + /* Register on-exit routine to delete the new segment */ + on_shmem_exit(pgwin32_SharedMemoryDelete, PointerGetDatum(hmap2)); + + *shim = hdr; + return hdr; +} + +/* + * PGSharedMemoryReAttach + * + * This is called during startup of a postmaster child process to re-attach to + * an already existing shared memory segment, using the handle inherited from + * the postmaster. + * + * ShmemProtectiveRegion, UsedShmemSegID and UsedShmemSegAddr are implicit + * parameters to this routine. The caller must have already restored them to + * the postmaster's values. + */ +void +PGSharedMemoryReAttach(void) +{ + PGShmemHeader *hdr; + void *origUsedShmemSegAddr = UsedShmemSegAddr; + + Assert(ShmemProtectiveRegion != NULL); + Assert(UsedShmemSegAddr != NULL); + Assert(IsUnderPostmaster); + + /* + * Release memory region reservations made by the postmaster + */ + if (VirtualFree(ShmemProtectiveRegion, 0, MEM_RELEASE) == 0) + elog(FATAL, "failed to release reserved memory region (addr=%p): error code %lu", + ShmemProtectiveRegion, GetLastError()); + if (VirtualFree(UsedShmemSegAddr, 0, MEM_RELEASE) == 0) + elog(FATAL, "failed to release reserved memory region (addr=%p): error code %lu", + UsedShmemSegAddr, GetLastError()); + + hdr = (PGShmemHeader *) MapViewOfFileEx(UsedShmemSegID, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0, UsedShmemSegAddr); + if (!hdr) + elog(FATAL, "could not reattach to shared memory (key=%p, addr=%p): error code %lu", + UsedShmemSegID, UsedShmemSegAddr, GetLastError()); + if (hdr != origUsedShmemSegAddr) + elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)", + hdr, origUsedShmemSegAddr); + if (hdr->magic != PGShmemMagic) + elog(FATAL, "reattaching to shared memory returned non-PostgreSQL memory"); + dsm_set_control_handle(hdr->dsm_control); + + UsedShmemSegAddr = hdr; /* probably redundant */ +} + +/* + * PGSharedMemoryNoReAttach + * + * This is called during startup of a postmaster child process when we choose + * *not* to re-attach to the existing shared memory segment. We must clean up + * to leave things in the appropriate state. + * + * The child process startup logic might or might not call PGSharedMemoryDetach + * after this; make sure that it will be a no-op if called. + * + * ShmemProtectiveRegion, UsedShmemSegID and UsedShmemSegAddr are implicit + * parameters to this routine. The caller must have already restored them to + * the postmaster's values. + */ +void +PGSharedMemoryNoReAttach(void) +{ + Assert(ShmemProtectiveRegion != NULL); + Assert(UsedShmemSegAddr != NULL); + Assert(IsUnderPostmaster); + + /* + * Under Windows we will not have mapped the segment, so we don't need to + * un-map it. Just reset UsedShmemSegAddr to show we're not attached. + */ + UsedShmemSegAddr = NULL; + + /* + * We *must* close the inherited shmem segment handle, else Windows will + * consider the existence of this process to mean it can't release the + * shmem segment yet. We can now use PGSharedMemoryDetach to do that. + */ + PGSharedMemoryDetach(); +} + +/* + * PGSharedMemoryDetach + * + * Detach from the shared memory segment, if still attached. This is not + * intended to be called explicitly by the process that originally created the + * segment (it will have an on_shmem_exit callback registered to do that). + * Rather, this is for subprocesses that have inherited an attachment and want + * to get rid of it. + * + * ShmemProtectiveRegion, UsedShmemSegID and UsedShmemSegAddr are implicit + * parameters to this routine. + */ +void +PGSharedMemoryDetach(void) +{ + /* + * Releasing the protective region liberates an unimportant quantity of + * address space, but be tidy. + */ + if (ShmemProtectiveRegion != NULL) + { + if (VirtualFree(ShmemProtectiveRegion, 0, MEM_RELEASE) == 0) + elog(LOG, "failed to release reserved memory region (addr=%p): error code %lu", + ShmemProtectiveRegion, GetLastError()); + + ShmemProtectiveRegion = NULL; + } + + /* Unmap the view, if it's mapped */ + if (UsedShmemSegAddr != NULL) + { + if (!UnmapViewOfFile(UsedShmemSegAddr)) + elog(LOG, "could not unmap view of shared memory: error code %lu", + GetLastError()); + + UsedShmemSegAddr = NULL; + } + + /* And close the shmem handle, if we have one */ + if (UsedShmemSegID != INVALID_HANDLE_VALUE) + { + if (!CloseHandle(UsedShmemSegID)) + elog(LOG, "could not close handle to shared memory: error code %lu", + GetLastError()); + + UsedShmemSegID = INVALID_HANDLE_VALUE; + } +} + + +/* + * pgwin32_SharedMemoryDelete + * + * Detach from and delete the shared memory segment + * (called as an on_shmem_exit callback, hence funny argument list) + */ +static void +pgwin32_SharedMemoryDelete(int status, Datum shmId) +{ + Assert(DatumGetPointer(shmId) == UsedShmemSegID); + PGSharedMemoryDetach(); +} + +/* + * pgwin32_ReserveSharedMemoryRegion(hChild) + * + * Reserve the memory region that will be used for shared memory in a child + * process. It is called before the child process starts, to make sure the + * memory is available. + * + * Once the child starts, DLLs loading in different order or threads getting + * scheduled differently may allocate memory which can conflict with the + * address space we need for our shared memory. By reserving the shared + * memory region before the child starts, and freeing it only just before we + * attempt to get access to the shared memory forces these allocations to + * be given different address ranges that don't conflict. + * + * NOTE! This function executes in the postmaster, and should for this + * reason not use elog(FATAL) since that would take down the postmaster. + */ +int +pgwin32_ReserveSharedMemoryRegion(HANDLE hChild) +{ + void *address; + + Assert(ShmemProtectiveRegion != NULL); + Assert(UsedShmemSegAddr != NULL); + Assert(UsedShmemSegSize != 0); + + /* ShmemProtectiveRegion */ + address = VirtualAllocEx(hChild, ShmemProtectiveRegion, + PROTECTIVE_REGION_SIZE, + MEM_RESERVE, PAGE_NOACCESS); + if (address == NULL) + { + /* Don't use FATAL since we're running in the postmaster */ + elog(LOG, "could not reserve shared memory region (addr=%p) for child %p: error code %lu", + ShmemProtectiveRegion, hChild, GetLastError()); + return false; + } + if (address != ShmemProtectiveRegion) + { + /* + * Should never happen - in theory if allocation granularity causes + * strange effects it could, so check just in case. + * + * Don't use FATAL since we're running in the postmaster. + */ + elog(LOG, "reserved shared memory region got incorrect address %p, expected %p", + address, ShmemProtectiveRegion); + return false; + } + + /* UsedShmemSegAddr */ + address = VirtualAllocEx(hChild, UsedShmemSegAddr, UsedShmemSegSize, + MEM_RESERVE, PAGE_READWRITE); + if (address == NULL) + { + elog(LOG, "could not reserve shared memory region (addr=%p) for child %p: error code %lu", + UsedShmemSegAddr, hChild, GetLastError()); + return false; + } + if (address != UsedShmemSegAddr) + { + elog(LOG, "reserved shared memory region got incorrect address %p, expected %p", + address, UsedShmemSegAddr); + return false; + } + + return true; +} |