1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 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()
|