tfdleak.nim 4.9 KB

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