diff options
Diffstat (limited to 'tests/stdlib/tfdleak.nim')
-rw-r--r-- | tests/stdlib/tfdleak.nim | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/tests/stdlib/tfdleak.nim b/tests/stdlib/tfdleak.nim new file mode 100644 index 000000000..272a7507c --- /dev/null +++ b/tests/stdlib/tfdleak.nim @@ -0,0 +1,152 @@ +discard """ + exitcode: 0 + output: "" + matrix: "; -d:nimInheritHandles; --mm:refc" + joinable: false +""" + +import os, osproc, strutils, nativesockets, net, selectors, memfiles, + asyncdispatch, asyncnet + +import std/[assertions, syncio] + +when defined(windows): + import winlean + + # Note: Windows 10-only API + proc compareObjectHandles(first, second: Handle): WINBOOL + {.stdcall, dynlib: "kernelbase", + importc: "CompareObjectHandles".} +else: + import posix + +proc leakCheck(f: AsyncFD | int | FileHandle | SocketHandle, msg: string, + expectLeak = defined(nimInheritHandles)) = + var args = @[$f.int, msg, $expectLeak] + + when defined(windows): + var refFd: Handle + # NOTE: This function shouldn't be used to duplicate sockets, + # as this function may mess with the socket internal refcounting. + # but due to the lack of type segmentation in the stdlib for + # Windows (AsyncFD can be a file or a socket), we will have to + # settle with this. + # + # Now, as a poor solution for the refcounting problem, we just + # simply let the duplicated handle leak. This should not interfere + # with the test since new handles can't occupy the slot held by + # the leaked ones. + if duplicateHandle(getCurrentProcess(), f.Handle, + getCurrentProcess(), addr refFd, + 0, 1, DUPLICATE_SAME_ACCESS) == 0: + raiseOSError osLastError(), "Couldn't create the reference handle" + args.add $refFd + + discard startProcess( + getAppFilename(), + args = args, + options = {poParentStreams} + ).waitForExit + +proc isValidHandle(f: int): bool = + ## Check if a handle is valid. Requires OS-native handles. + when defined(windows): + var flags: DWORD + result = getHandleInformation(f.Handle, addr flags) != 0 + else: + result = fcntl(f.cint, F_GETFD) != -1 + +proc main() = + if paramCount() == 0: + # Parent process + let f = syncio.open("__test_fdleak", fmReadWrite) + defer: close f + + leakCheck(f.getOsFileHandle, "system.open()") + + doAssert f.reopen("__test_fdleak2", fmReadWrite), "reopen failed" + + leakCheck(f.getOsFileHandle, "reopen") + + let sock = createNativeSocket() + defer: close sock + leakCheck(sock, "createNativeSocket()") + if sock.setInheritable(not defined(nimInheritHandles)): + leakCheck(sock, "createNativeSocket()", not defined(nimInheritHandles)) + else: + raiseOSError osLastError() + + let server = newSocket() + defer: close server + server.bindAddr(address = "127.0.0.1") + server.listen() + let (_, port) = server.getLocalAddr + + leakCheck(server.getFd, "newSocket()") + + let client = newSocket() + defer: close client + client.connect("127.0.0.1", port) + + var input: Socket + server.accept(input) + + leakCheck(input.getFd, "accept()") + + # ioselectors_select doesn't support returning a handle. + when not defined(windows): + let selector = newSelector[int]() + leakCheck(selector.getFd, "selector()", false) + + var mf = memfiles.open("__test_fdleak3", fmReadWrite, newFileSize = 1) + defer: close mf + when defined(windows): + leakCheck(mf.mapHandle, "memfiles.open().mapHandle", false) + else: + leakCheck(mf.handle, "memfiles.open().handle", false) + + let sockAsync = createAsyncNativeSocket() + defer: closeSocket sockAsync + leakCheck(sockAsync, "createAsyncNativeSocket()") + if sockAsync.setInheritable(not defined(nimInheritHandles)): + leakCheck(sockAsync, "createAsyncNativeSocket()", not defined(nimInheritHandles)) + else: + raiseOSError osLastError() + + let serverAsync = newAsyncSocket() + defer: close serverAsync + serverAsync.bindAddr(address = "127.0.0.1") + serverAsync.listen() + let (_, portAsync) = serverAsync.getLocalAddr + + leakCheck(serverAsync.getFd, "newAsyncSocket()") + + let clientAsync = newAsyncSocket() + defer: close clientAsync + waitFor clientAsync.connect("127.0.0.1", portAsync) + + let inputAsync = waitFor serverAsync.accept() + + leakCheck(inputAsync.getFd, "accept() async") + else: + let + fd = parseInt(paramStr 1) + expectLeak = parseBool(paramStr 3) + msg = (if expectLeak: "not " else: "") & "leaked " & paramStr 2 + let validHandle = + when defined(windows): + # On Windows, due to the use of winlean, causes the program to open + # a handle to the various dlls that's loaded. This handle might + # collide with the handle sent for testing. + # + # As a walkaround, we pass an another handle that's purposefully leaked + # as a reference so that we can verify whether the "leaked" handle + # is the right one. + let refFd = parseInt(paramStr 4) + fd.isValidHandle and compareObjectHandles(fd, refFd) != 0 + else: + fd.isValidHandle + if expectLeak xor validHandle: + echo msg + +when isMainModule: main() |