summary refs log tree commit diff stats
path: root/tests/stdlib/tfdleak.nim
blob: 272a7507c05377a7eb87036840604562b0d1cd6e (plain) (blame)
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()