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()