tfdleak.nim 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. discard """
  2. exitcode: 0
  3. output: ""
  4. matrix: "; -d:nimInheritHandles"
  5. joinable: false
  6. """
  7. import os, osproc, strutils, nativesockets, net, selectors, memfiles,
  8. asyncdispatch, asyncnet
  9. when defined(windows):
  10. import winlean
  11. # Note: Windows 10-only API
  12. proc compareObjectHandles(first, second: Handle): WINBOOL
  13. {.stdcall, dynlib: "kernelbase",
  14. importc: "CompareObjectHandles".}
  15. else:
  16. import posix
  17. proc leakCheck(f: AsyncFD | int | FileHandle | SocketHandle, msg: string,
  18. expectLeak = defined(nimInheritHandles)) =
  19. var args = @[$f.int, msg, $expectLeak]
  20. when defined(windows):
  21. var refFd: Handle
  22. # NOTE: This function shouldn't be used to duplicate sockets,
  23. # as this function may mess with the socket internal refcounting.
  24. # but due to the lack of type segmentation in the stdlib for
  25. # Windows (AsyncFD can be a file or a socket), we will have to
  26. # settle with this.
  27. #
  28. # Now, as a poor solution for the refcounting problem, we just
  29. # simply let the duplicated handle leak. This should not interfere
  30. # with the test since new handles can't occupy the slot held by
  31. # the leaked ones.
  32. if duplicateHandle(getCurrentProcess(), f.Handle,
  33. getCurrentProcess(), addr refFd,
  34. 0, 1, DUPLICATE_SAME_ACCESS) == 0:
  35. raiseOSError osLastError(), "Couldn't create the reference handle"
  36. args.add $refFd
  37. discard startProcess(
  38. getAppFilename(),
  39. args = args,
  40. options = {poParentStreams}
  41. ).waitForExit
  42. proc isValidHandle(f: int): bool =
  43. ## Check if a handle is valid. Requires OS-native handles.
  44. when defined(windows):
  45. var flags: DWORD
  46. result = getHandleInformation(f.Handle, addr flags) != 0
  47. else:
  48. result = fcntl(f.cint, F_GETFD) != -1
  49. proc main() =
  50. if paramCount() == 0:
  51. # Parent process
  52. let f = system.open("__test_fdleak", fmReadWrite)
  53. defer: close f
  54. leakCheck(f.getOsFileHandle, "system.open()")
  55. doAssert f.reopen("__test_fdleak2", fmReadWrite), "reopen failed"
  56. leakCheck(f.getOsFileHandle, "reopen")
  57. let sock = createNativeSocket()
  58. defer: close sock
  59. leakCheck(sock, "createNativeSocket()")
  60. if sock.setInheritable(not defined(nimInheritHandles)):
  61. leakCheck(sock, "createNativeSocket()", not defined(nimInheritHandles))
  62. else:
  63. raiseOSError osLastError()
  64. let server = newSocket()
  65. defer: close server
  66. server.bindAddr(address = "127.0.0.1")
  67. server.listen()
  68. let (_, port) = server.getLocalAddr
  69. leakCheck(server.getFd, "newSocket()")
  70. let client = newSocket()
  71. defer: close client
  72. client.connect("127.0.0.1", port)
  73. var input: Socket
  74. server.accept(input)
  75. leakCheck(input.getFd, "accept()")
  76. # ioselectors_select doesn't support returning a handle.
  77. when not defined(windows):
  78. let selector = newSelector[int]()
  79. leakCheck(selector.getFd, "selector()", false)
  80. var mf = memfiles.open("__test_fdleak3", fmReadWrite, newFileSize = 1)
  81. defer: close mf
  82. when defined(windows):
  83. leakCheck(mf.mapHandle, "memfiles.open().mapHandle", false)
  84. else:
  85. leakCheck(mf.handle, "memfiles.open().handle", false)
  86. let sockAsync = createAsyncNativeSocket()
  87. defer: closeSocket sockAsync
  88. leakCheck(sockAsync, "createAsyncNativeSocket()")
  89. if sockAsync.setInheritable(not defined(nimInheritHandles)):
  90. leakCheck(sockAsync, "createAsyncNativeSocket()", not defined(nimInheritHandles))
  91. else:
  92. raiseOSError osLastError()
  93. let serverAsync = newAsyncSocket()
  94. defer: close serverAsync
  95. serverAsync.bindAddr(address = "127.0.0.1")
  96. serverAsync.listen()
  97. let (_, portAsync) = serverAsync.getLocalAddr
  98. leakCheck(serverAsync.getFd, "newAsyncSocket()")
  99. let clientAsync = newAsyncSocket()
  100. defer: close clientAsync
  101. waitFor clientAsync.connect("127.0.0.1", portAsync)
  102. let inputAsync = waitFor serverAsync.accept()
  103. leakCheck(inputAsync.getFd, "accept() async")
  104. else:
  105. let
  106. fd = parseInt(paramStr 1)
  107. expectLeak = parseBool(paramStr 3)
  108. msg = (if expectLeak: "not " else: "") & "leaked " & paramStr 2
  109. let validHandle =
  110. when defined(windows):
  111. # On Windows, due to the use of winlean, causes the program to open
  112. # a handle to the various dlls that's loaded. This handle might
  113. # collide with the handle sent for testing.
  114. #
  115. # As a walkaround, we pass an another handle that's purposefully leaked
  116. # as a reference so that we can verify whether the "leaked" handle
  117. # is the right one.
  118. let refFd = parseInt(paramStr 4)
  119. fd.isValidHandle and compareObjectHandles(fd, refFd) != 0
  120. else:
  121. fd.isValidHandle
  122. if expectLeak xor validHandle:
  123. echo msg
  124. when isMainModule: main()