* tfdleak_multiple: introduce stress tester for tfdleak Imported from #14548 and tweaked for consumption by testament. This test seems to be really good at bringing out the flakyness of tfdleadk. Co-authored-by: Timothee Cour <timothee.cour2@gmail.com> * tfdleak: increase accuracy of the test on Windows This commit implements a new testing strategy for Windows: 1. We duplicate the handle that will be tested and enable inheritance. This duplicate will serve as a reference handle. 2. In addition to checking whether the handle is valid, we also verify whether the handle is the same as the reference. This gives us complete certainty on whether the handle in question is inherited from the parent. A side effect is that this uses Windows 10+ APIs. But since this is just for the test, we don't have to be picky about it. Ideally we would want to do something like this for other POSIX-based system, but most of them lack a facility to do this, and as of writing there isn't any false positive for them, so we won't need the additional checks. MemFile.fHandle will also no longer be tested, as this handle defaults to being invalid. Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
148 lines
4.8 KiB
Nim
148 lines
4.8 KiB
Nim
discard """
|
|
exitcode: 0
|
|
output: ""
|
|
matrix: "; -d:nimInheritHandles"
|
|
"""
|
|
|
|
import os, osproc, strutils, nativesockets, net, selectors, memfiles,
|
|
asyncdispatch, asyncnet
|
|
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 = system.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()
|