├── .github └── workflows │ └── ci.yml ├── LICENSE ├── README.md ├── asynctools.nim ├── asynctools.nimble ├── asynctools ├── asyncdns.nim ├── asyncipc.nim ├── asyncpipe.nim ├── asyncproc.nim ├── asyncpty.nim └── asyncsync.nim ├── doc └── empty.txt └── tests ├── bootstrap.bat └── bootstrap.sh /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: asynctools 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | fail-fast: false 8 | max-parallel: 20 9 | matrix: 10 | branch: [master] 11 | target: 12 | - os: linux 13 | cpu: amd64 14 | nim_branch: devel 15 | - os: linux 16 | cpu: amd64 17 | nim_branch: v1.2.12 18 | - os: linux 19 | cpu: amd64 20 | nim_branch: v1.4.8 21 | - os: linux 22 | cpu: i386 23 | nim_branch: devel 24 | - os: linux 25 | cpu: i386 26 | nim_branch: v1.2.12 27 | - os: linux 28 | cpu: i386 29 | nim_branch: v1.4.8 30 | - os: macos 31 | cpu: amd64 32 | nim_branch: devel 33 | - os: macos 34 | cpu: amd64 35 | nim_branch: v1.2.12 36 | - os: macos 37 | cpu: amd64 38 | nim_branch: v1.4.8 39 | - os: windows 40 | cpu: amd64 41 | nim_branch: devel 42 | - os: windows 43 | cpu: amd64 44 | nim_branch: v1.2.12 45 | - os: windows 46 | cpu: amd64 47 | nim_branch: v1.4.8 48 | - os: windows 49 | cpu: i386 50 | nim_branch: devel 51 | - os: windows 52 | cpu: i386 53 | nim_branch: v1.2.12 54 | - os: windows 55 | cpu: i386 56 | nim_branch: v1.4.8 57 | include: 58 | - target: 59 | os: linux 60 | builder: ubuntu-18.04 61 | - target: 62 | os: macos 63 | builder: macos-10.15 64 | - target: 65 | os: windows 66 | builder: windows-2019 67 | 68 | name: '${{ matrix.target.os }}-${{ matrix.target.cpu }}-nim-${{ matrix.target.nim_branch }} (${{ matrix.branch }})' 69 | runs-on: ${{ matrix.builder }} 70 | env: 71 | NIM_DIR: nim-${{ matrix.target.nim_branch }}-${{ matrix.target.cpu }} 72 | NIM_BRANCH: ${{ matrix.target.nim_branch }} 73 | NIM_ARCH: ${{ matrix.target.cpu }} 74 | steps: 75 | - name: Checkout asynctools 76 | uses: actions/checkout@v2 77 | with: 78 | path: asynctools 79 | submodules: false 80 | 81 | - name: Restore MinGW-W64 (Windows) from cache 82 | if: runner.os == 'Windows' 83 | id: windows-mingw-cache 84 | uses: actions/cache@v2 85 | with: 86 | path: external/mingw-${{ matrix.target.cpu }} 87 | key: 'mingw-${{ matrix.target.cpu }}' 88 | 89 | - name: Restore Nim DLLs dependencies (Windows) from cache 90 | if: runner.os == 'Windows' 91 | id: windows-dlls-cache 92 | uses: actions/cache@v2 93 | with: 94 | path: external/dlls-${{ matrix.target.cpu }} 95 | key: 'dlls-${{ matrix.target.cpu }}' 96 | 97 | - name: Install MinGW64 dependency (Windows) 98 | if: > 99 | steps.windows-mingw-cache.outputs.cache-hit != 'true' && 100 | runner.os == 'Windows' 101 | shell: bash 102 | run: | 103 | mkdir -p external 104 | if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then 105 | MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/11.1.0-12.0.0-9.0.0-r2/winlibs-x86_64-posix-seh-gcc-11.1.0-mingw-w64-9.0.0-r2.7z" 106 | ARCH=64 107 | else 108 | MINGW_URL="https://github.com/brechtsanders/winlibs_mingw/releases/download/11.1.0-12.0.0-9.0.0-r2/winlibs-i686-posix-dwarf-gcc-11.1.0-mingw-w64-9.0.0-r2.7z" 109 | ARCH=32 110 | fi 111 | curl -L "$MINGW_URL" -o "external/mingw-${{ matrix.target.cpu }}.7z" 112 | 7z x -y "external/mingw-${{ matrix.target.cpu }}.7z" -oexternal/ 113 | mv external/mingw$ARCH external/mingw-${{ matrix.target.cpu }} 114 | 115 | - name: Install DLLs dependencies (Windows) 116 | if: > 117 | steps.windows-dlls-cache.outputs.cache-hit != 'true' && 118 | runner.os == 'Windows' 119 | shell: bash 120 | run: | 121 | mkdir -p external 122 | curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip 123 | 7z x -y external/windeps.zip -oexternal/dlls-${{ matrix.target.cpu }} 124 | 125 | - name: Path to cached dependencies (Windows) 126 | if: > 127 | runner.os == 'Windows' 128 | shell: bash 129 | run: | 130 | echo '${{ github.workspace }}'"/external/mingw-${{ matrix.target.cpu }}/bin" >> $GITHUB_PATH 131 | echo '${{ github.workspace }}'"/external/dlls-${{ matrix.target.cpu }}" >> $GITHUB_PATH 132 | 133 | - name: Restore Nim from cache 134 | if: > 135 | steps.nim-compiler-cache.outputs.cache-hit != 'true' && 136 | matrix.target.nim_branch != 'devel' 137 | id: nim-compiler-cache 138 | uses: actions/cache@v2 139 | with: 140 | path: '${{ github.workspace }}/nim-${{ matrix.target.nim_branch }}-${{ matrix.target.cpu }}' 141 | key: 'nim-${{ matrix.target.cpu }}-${{ matrix.target.nim_branch }}' 142 | 143 | - name: Build Nim and associated tools 144 | shell: bash 145 | run: | 146 | pwd 147 | ls -la 148 | if [[ '${{ matrix.target.os }}' == 'windows' ]]; then 149 | asynctools/tests/bootstrap.bat 150 | else 151 | asynctools/tests/bootstrap.sh 152 | fi 153 | 154 | - name: Setup environment 155 | shell: bash 156 | run: | 157 | echo '${{ github.workspace }}'"/${NIM_DIR}/bin" >> $GITHUB_PATH 158 | 159 | - name: Run asynctools tests 160 | shell: bash 161 | working-directory: asynctools 162 | run: | 163 | nimble install -y 164 | nimble test 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Eugene Kabanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asynctools 2 | Various asynchronous modules for Nim Language [http://www.nim-lang.org](http://nim-lang.org/). 3 | 4 | ## Main features 5 | 6 | [**asyncpipe.nim**](asynctools/asyncpipe.nim) 7 | 8 | Asynchronous pipes, using non-blocking pipe(2) on Linux/BSD/MacOS/Solaris and named pipes on Windows. 9 | 10 | [**asyncipc.nim**](asynctools/asyncipc.nim) 11 | 12 | Asynchronous inter-process communication, using non-blocking mkfifo(3) on Linux/BSD/MacOS/Solaris and named memory maps on Windows. 13 | 14 | [**asyncproc.nim**](asynctools/asyncproc.nim) 15 | 16 | Asynchronous process manipulation facility with asynchronous pipes as standart input/output/error handles, and asynchronous. 17 | 18 | [**asyncdns.nim**](asynctools/asyncdns.nim) 19 | 20 | Asynchronous DNS resolver, using default libresolv/libbind on Linux/BSD/MacOS/Solaris, and default dnsapi.dll on Windows. 21 | 22 | [**asyncpty.nim**](asynctools/asyncpty.nim) 23 | 24 | Asynchronous PTY communication, using pty mechanism of Linux/BSD/MacOS/Solaris, and named pipes on Windows. 25 | 26 | [**asyncsync.nim**](asynctools/asyncsync.nim) 27 | 28 | Asynchronous synchronization primitives, such as Queue, Lock and Event. 29 | 30 | ## Installation 31 | 32 | The most recent version of the modules can be installed directly from GitHub repository 33 | 34 | ``` 35 | $ nimble install https://github.com/cheatfate/asynctools.git 36 | ``` 37 | 38 | ## Minimal requirements 39 | 40 | - Nim language compiler 0.14.2 41 | 42 | ## Documentation 43 | 44 | Every module have documentation inside, you can obtain it via 45 | 46 | ``` 47 | $ nim doc 48 | ``` 49 | -------------------------------------------------------------------------------- /asynctools.nim: -------------------------------------------------------------------------------- 1 | import asynctools/asyncpipe, asynctools/asyncipc, asynctools/asyncdns 2 | import asynctools/asyncproc, asynctools/asyncpty, asynctools/asyncsync 3 | export asyncpipe, asyncipc, asyncdns, asyncproc, asyncpty, asyncsync 4 | -------------------------------------------------------------------------------- /asynctools.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.1.1" 3 | author = "Eugene Kabanov" 4 | description = "Various asynchronous tools for Nim" 5 | license = "MIT" 6 | 7 | # Deps 8 | requires "nim >= 0.19.4" 9 | 10 | task test, "Runs the test suite": 11 | var testCommands = @[ 12 | "asynctools/asyncsync", 13 | "asynctools/asyncpty", 14 | "asynctools/asyncproc", 15 | "asynctools/asyncpipe", 16 | "asynctools/asyncipc", 17 | "asynctools/asyncdns" 18 | ] 19 | 20 | for cmd in testCommands: 21 | exec "nim c -f -r " & cmd & ".nim" 22 | rmFile(cmd.toExe()) 23 | 24 | when (NimMajor, NimMinor) >= (1, 5): 25 | for cmd in testCommands: 26 | exec "nim c -f --gc:orc -r " & cmd & ".nim" 27 | rmFile(cmd.toExe()) 28 | -------------------------------------------------------------------------------- /asynctools/asyncdns.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Asynchronous tools for Nim Language 4 | # (c) Copyright 2016 Eugene Kabanov 5 | # 6 | # See the file "LICENSE", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## This module implements asynchronous DNS resolve mechanism. 11 | ## 12 | ## asyncGetAddrInfo() don't have support for `flags` argument. 13 | ## 14 | ## Supported platforms: Linux, Windows, MacOS, FreeBSD, NetBSD, OpenBSD(*), 15 | ## Solaris. 16 | ## 17 | ## * OpenBSD requires `libbind` package. 18 | 19 | import asyncdispatch, os, nativesockets, strutils 20 | 21 | const 22 | PACKETSZ = 512 23 | 24 | type 25 | uncheckedArray [T] = UncheckedArray[T] 26 | AsyncAddrInfo* = distinct AddrInfo 27 | 28 | when defined(windows): 29 | import winlean 30 | else: 31 | import posix 32 | 33 | proc `$`*(aip: ptr AddrInfo|ptr AsyncAddrInfo): string = 34 | result = "" 35 | var ai = cast[ptr AddrInfo](aip) 36 | var address = newString(128) 37 | var nport = 0'u16 38 | var hport = 0'u16 39 | if ai.ai_family == toInt(Domain.AF_INET6): 40 | let p = cast[ptr Sockaddr_in6](ai.ai_addr) 41 | nport = p.sin6_port 42 | hport = nativesockets.ntohs(nport) 43 | discard inet_ntop(ai.ai_family, cast[pointer](addr p.sin6_addr), 44 | cstring(address), len(address).int32) 45 | else: 46 | let p = cast[ptr Sockaddr_in](ai.ai_addr) 47 | nport = p.sin_port 48 | hport = nativesockets.ntohs(nport) 49 | discard inet_ntop(ai.ai_family, cast[pointer](addr p.sin_addr), 50 | cstring(address), len(address).int32) 51 | result &= "ai_flags = 0x" & toHex(cast[int32](ai.ai_flags)) & "\n" 52 | result &= "ai_family = 0x" & toHex(cast[int32](ai.ai_family)) & "\n" 53 | result &= "ai_socktype = 0x" & toHex(cast[int32](ai.ai_socktype)) & "\n" 54 | result &= "ai_protocol = 0x" & toHex(cast[int32](ai.ai_protocol)) & "\n" 55 | result &= "ai_canonname = 0x" & toHex(cast[int](ai.ai_canonname)) & "\n" 56 | result &= "ai_addrlen = " & $ai.ai_addrlen & "\n" 57 | result &= "ai_addr = 0x" & toHex(cast[int](ai.ai_addr)) & "\n" 58 | result &= " sin_family = 0x" & toHex(cast[int16](ai.ai_addr.sa_family)) & 59 | "\n" 60 | result &= " sin_port = 0x" & toHex(cast[int16](nport)) & " (" & 61 | $hport & ")" & "\n" 62 | result &= " sin_addr = " & address & "\n" 63 | result &= "ai_next = 0x" & toHex(cast[int](ai.ai_next)) 64 | 65 | proc `==`*(ai1: ptr AddrInfo|ptr AsyncAddrInfo, 66 | ai2: ptr AddrInfo|ptr AsyncAddrInfo): bool = 67 | result = false 68 | var sai = cast[ptr AddrInfo](ai1) 69 | var dai = cast[ptr AddrInfo](ai2) 70 | 71 | var saiLength = 0 72 | var daiLength = 0 73 | var rec = sai 74 | while not rec.isNil: 75 | inc(saiLength) 76 | rec = rec.ai_next 77 | rec = dai 78 | while not rec.isNil: 79 | inc(daiLength) 80 | rec = rec.ai_next 81 | 82 | if saiLength == daiLength: 83 | var srec = sai 84 | while not srec.isNil: 85 | result = false 86 | var drec = dai 87 | while not drec.isNil: 88 | if srec.ai_family == drec.ai_family and 89 | srec.ai_socktype == drec.ai_socktype and 90 | srec.ai_protocol == drec.ai_protocol and 91 | srec.ai_addrlen == drec.ai_addrlen: 92 | if equalMem(cast[pointer](srec.ai_addr), cast[pointer](drec.ai_addr), 93 | srec.ai_addrlen): 94 | result = true 95 | break 96 | drec = drec.ai_next 97 | if not result: 98 | break 99 | srec = srec.ai_next 100 | 101 | when defined(windows): 102 | 103 | const 104 | DnsConfigDnsServerList = 6 105 | DnsTypeA = 1 106 | DnsTypeAAAA = 28 107 | DnsFreeRecordList = 1 108 | DnsSectionAnswer = 1 109 | DNS_ERROR_NO_DNS_SERVERS = 9852 110 | DNS_INFO_NO_RECORDS = 9501 111 | DNS_ERROR_WRONG_XID = 100 112 | type 113 | WORD = uint16 114 | 115 | DNS_RECORD = object 116 | pNext: ptr DNS_RECORD 117 | pName: pointer 118 | wType: Word 119 | wDataLength: Word 120 | flags: Dword 121 | ttl: Dword 122 | reserved: Dword 123 | data: byte 124 | PDNS_RECORD = ptr DNS_RECORD 125 | 126 | DNS_HEADER {.packed.} = object 127 | Xid: Word 128 | Flags: Word 129 | QuestionCount: Word 130 | AnswerCount: Word 131 | NameServerCount: Word 132 | AdditionalCount: Word 133 | 134 | proc dnsQueryConfig(config: Dword, flag: Dword, adapterName: pointer, 135 | reserved: pointer, buffer: pointer, 136 | buflen: ptr Dword): LONG 137 | {.importc: "DnsQueryConfig", stdcall, dynlib: "dnsapi.dll".} 138 | proc dnsWriteQuestionToBuffer(buffer: pointer, buflen: ptr Dword, 139 | name: WideCSTring, wtype: Word, 140 | xid: Word, recursion: Winbool): Winbool 141 | {.importc: "DnsWriteQuestionToBuffer_W", stdcall, 142 | dynlib: "dnsapi.dll".} 143 | proc dnsExtractRecordsFromMessage(message: pointer, buflen: Word, 144 | buffer: ptr PDNS_RECORD): LONG 145 | {.importc: "DnsExtractRecordsFromMessage_W", stdcall, 146 | dynlib: "dnsapi.dll".} 147 | proc dnsFree(buffer: pointer, ftype: Dword) 148 | {.importc: "DnsFree", stdcall, dynlib: "dnsapi.dll".} 149 | 150 | proc QueryPerformanceCounter(res: var int64) 151 | {.importc: "QueryPerformanceCounter", stdcall, dynlib: "kernel32".} 152 | 153 | proc getDnsServersList(): ptr uncheckedArray[int32] = 154 | var buffer: pointer = nil 155 | var buflen = 0.Dword 156 | let res = dnsQueryConfig(DnsConfigDnsServerList, 0, nil, nil, buffer, 157 | addr buflen) 158 | if res == 0 and buflen > 4: 159 | buffer = alloc0(buflen) 160 | let sres = dnsQueryConfig(DnsConfigDnsServerList, 0, nil, nil, buffer, 161 | addr buflen) 162 | if sres == 0: 163 | result = cast[ptr uncheckedArray[int32]](buffer) 164 | else: 165 | raiseOsError(osLastError()) 166 | else: 167 | raiseOsError(osLastError()) 168 | 169 | proc freeDnsServersList(arr: ptr uncheckedArray[int32]) = 170 | dealloc(cast[pointer](arr)) 171 | 172 | proc getXid(): Word = 173 | var number = 0'i64 174 | QueryPerformanceCounter(number) 175 | result = ((number shr 48) and 0xFFFF).Word xor 176 | ((number shr 32) and 0xFFFF).Word xor 177 | ((number shr 16) and 0xFFFF).Word xor 178 | (number and 0xFFFF).Word 179 | 180 | when declared(system.csize_t): 181 | type SizeAlias = system.csize_t 182 | else: 183 | type SizeAlias = system.int 184 | 185 | proc asyncGetAddrInfo*(address: string, port: Port, 186 | domain: Domain = nativesockets.AF_INET, 187 | sockType: SockType = nativesockets.SOCK_STREAM, 188 | protocol: Protocol = nativesockets.IPPROTO_TCP): 189 | Future[ptr AsyncAddrInfo] {.async.} = 190 | var blob: pointer = nil 191 | var ai = 0 192 | var request = newString(PACKETSZ) 193 | var response = newString(PACKETSZ) 194 | var reqLength = 0 195 | var nsLastError = 0 196 | 197 | var qt = if domain == Domain.AF_INET6: Word(DnsTypeAAAA) else: 198 | Word(DnsTypeA) 199 | var nsList = getDnsServersList() 200 | if nsList[0] <= 0: 201 | raiseOsError(OSErrorCode(DNS_ERROR_NO_DNS_SERVERS)) 202 | 203 | var xid = getXid() 204 | var buflen = 0.Dword 205 | var buffer: pointer = nil 206 | while true: 207 | let res = dnsWriteQuestionToBuffer(buffer, addr buflen, 208 | newWideCString(address), qt, 209 | xid, 0) 210 | if res == 0: 211 | if buflen > 0 and isNil(buffer): 212 | buffer = alloc0(buflen) 213 | else: 214 | if not isNil(buffer): 215 | dealloc(buffer) 216 | raiseOsError(osLastError()) 217 | elif res == 1: 218 | if buflen > PACKETSZ: 219 | if not isNil(buffer): 220 | dealloc(buffer) 221 | raise newException(ValueError, "Packet size is too big!") 222 | else: 223 | copyMem(cast[pointer](addr request[0]), buffer, buflen) 224 | reqLength = buflen 225 | dealloc(buffer) 226 | break 227 | 228 | let sock = createAsyncNativeSocket(nativesockets.AF_INET, 229 | nativesockets.SOCK_DGRAM, 230 | Protocol.IPPROTO_UDP) 231 | 232 | for i in 1..nsList[0]: 233 | var dnsAddr = Sockaddr_in() 234 | var recvAddr = Sockaddr_in() 235 | var recvALen = sizeof(Sockaddr_in) 236 | dnsAddr.sin_family = winlean.AF_INET 237 | dnsAddr.sin_port = nativesockets.htons(53.uint16) 238 | dnsAddr.sin_addr.s_addr = nsList[i].uint32 239 | 240 | await sendTo(sock, addr request[0], reqLength, 241 | cast[ptr SockAddr](addr dnsAddr), 242 | sizeof(Sockaddr_in).SockLen) 243 | 244 | var respLength = await recvFromInto(sock, addr(response[0]), 245 | PACKETSZ, 246 | cast[ptr SockAddr](addr recvAddr), 247 | cast[ptr SockLen](addr recvALen)) 248 | var records: PDNS_RECORD = nil 249 | 250 | # This is emulation of windns.h DNS_BYTE_FLIP_HEADER_COUNTS macro. 251 | var header = cast[ptr DNS_HEADER](addr response[0]) 252 | header.Xid = nativesockets.ntohs(header.Xid) 253 | header.QuestionCount = nativesockets.ntohs(header.QuestionCount) 254 | header.AnswerCount = nativesockets.ntohs(header.AnswerCount) 255 | header.NameServerCount = nativesockets.ntohs(header.NameServerCount) 256 | header.AdditionalCount = nativesockets.ntohs(header.AdditionalCount) 257 | 258 | if header.Xid != xid: 259 | nsLastError = DNS_ERROR_WRONG_XID 260 | continue 261 | 262 | let res = dnsExtractRecordsFromMessage(cast[pointer](addr response[0]), 263 | respLength.Word, addr records) 264 | if res != 0: 265 | nsLastError = res.int 266 | continue 267 | 268 | # lets count answer records 269 | var count = 0 270 | var rec = records 271 | while rec != nil: 272 | let section = rec.flags and 3 273 | if (section == DnsSectionAnswer) and 274 | ((rec.wType == DnsTypeA) or (rec.wType == DnsTypeAAAA)): 275 | inc(count) 276 | rec = rec.pNext 277 | 278 | if count == 0: 279 | nsLastError = DNS_INFO_NO_RECORDS 280 | continue 281 | 282 | let blobsize = sizeof(AddrInfo) * count + sizeof(SockAddr) * count 283 | if isNil(blob): 284 | blob = alloc0(blobsize) 285 | 286 | let p0 = blob 287 | let p1 = cast[pointer](cast[uint](blob) + 288 | cast[uint](sizeof(AddrInfo) * count)) 289 | var addrArr = cast[ptr uncheckedArray[AddrInfo]](p0) 290 | var sockArr = cast[ptr uncheckedArray[SockAddr]](p1) 291 | 292 | var k = 0 293 | rec = records 294 | while rec != nil: 295 | let section = rec.flags and 3 296 | 297 | if section == DnsSectionAnswer: 298 | if rec.wType == DnsTypeA: 299 | addrArr[ai].ai_family = toInt(domain) 300 | addrArr[ai].ai_socktype = toInt(sockType) 301 | addrArr[ai].ai_protocol = toInt(protocol) 302 | addrArr[ai].ai_addrlen = sizeof(Sockaddr_in).SizeAlias 303 | addrArr[ai].ai_addr = addr sockArr[ai] 304 | var addrp = cast[ptr Sockaddr_in](addr sockArr[ai]) 305 | addrp.sin_family = toInt(domain).uint16 306 | addrp.sin_port = nativesockets.ntohs(cast[uint16](port)) 307 | copyMem(addr addrp.sin_addr, addr rec.data, 4) 308 | if k + 1 < count: 309 | addrArr[ai].ai_next = cast[ptr AddrInfo](addr addrarr[ai + 1]) 310 | inc(ai) 311 | elif rec.wType == DnsTypeAAAA: 312 | addrArr[ai].ai_family = toInt(domain) 313 | addrArr[ai].ai_socktype = toInt(sockType) 314 | addrArr[ai].ai_protocol = toInt(protocol) 315 | addrArr[ai].ai_addrlen = sizeof(Sockaddr_in6).SizeAlias 316 | addrArr[ai].ai_addr = addr sockArr[ai] 317 | var addrp = cast[ptr Sockaddr_in6](addr sockArr[ai]) 318 | addrp.sin6_family = toInt(domain).uint16 319 | addrp.sin6_port = nativesockets.ntohs(cast[uint16](port)) 320 | copyMem(addr addrp.sin6_addr, addr rec.data, 4 * 4) 321 | if k + 1 < count: 322 | addrArr[ai].ai_next = cast[ptr AddrInfo](addr addrarr[ai + 1]) 323 | inc(ai) 324 | inc(k) 325 | rec = rec.pNext 326 | 327 | dnsFree(cast[pointer](records), DnsFreeRecordList) 328 | 329 | if ai > 0 and nsLastError == 0: 330 | result = cast[ptr AsyncAddrInfo](blob) 331 | break 332 | else: 333 | ai = 0 334 | zeroMem(blob, blobsize) 335 | continue 336 | 337 | freeDnsServersList(nsList) 338 | if nsLastError == 0: 339 | discard 340 | elif nsLastError == DNS_ERROR_WRONG_XID: 341 | raise newException(ValueError, "Wrong packet id recieved!") 342 | else: 343 | raiseOsError(OSErrorCode(nsLastError)) 344 | 345 | else: 346 | when defined(linux) or defined(macosx): 347 | {.passL: "-lresolv".} 348 | 349 | when defined(freebsd) or defined(linux) or defined(macosx): 350 | const headers = """#include 351 | #include 352 | #include 353 | #include """ 354 | elif defined(solaris): 355 | const headers = """#include 356 | #include 357 | #include 358 | #include 359 | #include """ 360 | elif defined(netbsd): 361 | const headers = """#include 362 | #include """ 363 | elif defined(openbsd): 364 | {.hint: "*** OpenBSD requires `libbind` package to be installed".} 365 | const headers = """#include 366 | #include 367 | #include 368 | #include """ 369 | {.passC: "-I/usr/local/include/bind".} 370 | {.passL: "-L/usr/local/lib/libbind -R/usr/local/lib/libbind -lbind".} 371 | else: 372 | {.error: "Unsupported operation system!".} 373 | 374 | const 375 | QUERY = 0 376 | C_IN = 1 377 | NS_MAXDNAME = 1025 378 | 379 | type 380 | adnsStatus = enum 381 | noError, formatError, serverFailError, nxDomainError, 382 | notimplError, refusedError, badDataError, parseError, 383 | recordError, unknownError, zeroError, rtypeError 384 | 385 | ResolverState {.importc: "struct __res_state", 386 | header: headers, pure, final.} = object 387 | retrans {.importc: "retrans".}: cint 388 | retry {.importc: "retry".}: cint 389 | options {.importc: "options".}: culong 390 | nscount {.importc: "nscount".}: cint 391 | nsaddrList {.importc: "nsaddr_list".}: array[3, Sockaddr_in] 392 | # id {.importc: "id".}: cushort 393 | # dnsrch {.importc: "dnsrch".}: array[7, ptr char] 394 | # defdname {.importc: "defdname".}: array[256, char] 395 | # pfcode {.importc: "pfcode".}: culong 396 | # somethings: array[4, char] 397 | # sortList {.importc: "sort_list".}: array[10, SortList] 398 | # qhook {.importc: "qhook".}: pointer 399 | # rhook {.importc: "rhook".}: pointer 400 | # resErrno {.importc: "res_h_errno".}: cint 401 | # vcsock {.importc: "_vcsock".}: cint 402 | # flags {.importc: "_flags".}: cuint 403 | 404 | PResolver = ref ResolverState 405 | 406 | nsFlag {.importc: "enum __ns_flag", header: headers.} = enum 407 | # ns_f_qr, # Question/Response. 408 | # ns_f_opcode, # Operation code. 409 | # ns_f_aa, # Authoritative Answer. 410 | # ns_f_tc, # Truncation occurred. 411 | # ns_f_rd, # Recursion Desired. 412 | # ns_f_ra, # Recursion Available. 413 | # ns_f_z, # MBZ. 414 | # ns_f_ad, # Authentic Data (DNSSEC). 415 | # ns_f_cd, # Checking Disabled (DNSSEC). 416 | ns_f_rcode = 9, # Response code. 417 | # ns_f_max 418 | 419 | nsRcode {.importc: "enum __ns_rcode", header: headers.} = enum 420 | ns_r_noerror = 0, # No error occurred. 421 | ns_r_formerr = 1, # Format error. 422 | ns_r_servfail = 2, # Server failure. 423 | ns_r_nxdomain = 3, # Name error. 424 | ns_r_notimpl = 4, # Unimplemented. 425 | ns_r_refused = 5, # Operation refused. 426 | # these are for BIND_UPDATE 427 | # ns_r_yxdomain = 6, # Name exists 428 | # ns_r_yxrrset = 7, # RRset exists 429 | # ns_r_nxrrset = 8, # RRset does not exist 430 | # ns_r_notauth = 9, # Not authoritative for zone 431 | # ns_r_notzone = 10, # Zone of record different from zone section 432 | # ns_r_max = 11, 433 | # # The following are EDNS extended rcodes 434 | # # ns_r_badvers = 16, 435 | # # The following are TSIG errors 436 | # ns_r_badsig = 16, 437 | # ns_r_badkey = 17, 438 | # ns_r_badtime = 18 439 | 440 | nsSect {.importc: "enum __ns_sect", header: headers.} = enum 441 | # ns_s_qd = 0, # Query: Question. 442 | ns_s_an = 1, # Query: Answer. 443 | # ns_s_ns = 2, # Query: Name servers. 444 | # ns_s_ar = 3, # Query|Update: Additional records. 445 | # ns_s_max = 4 446 | 447 | nsType {.importc: "enum __ns_type", header: headers.} = enum 448 | # ns_t_invalid = 0, # Cookie. 449 | ns_t_a = 1, # Host address. 450 | # ns_t_ns = 2, # Authoritative server. 451 | # ns_t_md = 3, # Mail destination. 452 | # ns_t_mf = 4, # Mail forwarder. 453 | ns_t_cname = 5, # Canonical name. 454 | # ns_t_soa = 6, # Start of authority zone. 455 | # ns_t_mb = 7, # Mailbox domain name. 456 | # ns_t_mg = 8, # Mail group member. 457 | # ns_t_mr = 9, # Mail rename name. 458 | # ns_t_null = 10, # Null resource record. 459 | # ns_t_wks = 11, # Well known service. 460 | # ns_t_ptr = 12, # Domain name pointer. 461 | # ns_t_hinfo = 13, # Host information. 462 | # ns_t_minfo = 14, # Mailbox information. 463 | # ns_t_mx = 15, # Mail routing information. 464 | # ns_t_txt = 16, # Text strings. 465 | # ns_t_rp = 17, # Responsible person. 466 | # ns_t_afsdb = 18, # AFS cell database. 467 | # ns_t_x25 = 19, # X_25 calling address. 468 | # ns_t_isdn = 20, # ISDN calling address. 469 | # ns_t_rt = 21, # Router. 470 | # ns_t_nsap = 22, # NSAP address. 471 | # ns_t_nsap_ptr = 23, # Reverse NSAP lookup (deprecated). 472 | # ns_t_sig = 24, # Security signature. 473 | # ns_t_key = 25, # Security key. 474 | # ns_t_px = 26, # X.400 mail mapping. 475 | # ns_t_gpos = 27, # Geographical position (withdrawn). 476 | ns_t_aaaa = 28, # IPv6 Address. 477 | # ns_t_loc = 29, # Location Information. 478 | # ns_t_nxt = 30, # Next domain (security). 479 | # ns_t_eid = 31, # Endpoint identifier. 480 | # ns_t_nimloc = 32, # Nimrod Locator. 481 | # ns_t_srv = 33, # Server Selection. 482 | # ns_t_atma = 34, # ATM Address 483 | # ns_t_naptr = 35, # Naming Authority PoinTeR 484 | # ns_t_kx = 36, # Key Exchange 485 | # ns_t_cert = 37, # Certification record 486 | # ns_t_a6 = 38, # IPv6 address (experimental) 487 | # ns_t_dname = 39, # Non-terminal DNAME 488 | # ns_t_sink = 40, # Kitchen sink (experimentatl) 489 | # ns_t_opt = 41, # EDNS0 option (meta-RR) 490 | # ns_t_apl = 42, # Address prefix list (RFC3123) 491 | # ns_t_ds = 43, # Delegation Signer 492 | # ns_t_sshfp = 44, # SSH Fingerprint 493 | # ns_t_ipseckey = 45, # IPSEC Key 494 | # ns_t_rrsig = 46, # RRset Signature 495 | # ns_t_nsec = 47, # Negative security 496 | # ns_t_dnskey = 48, # DNS Key 497 | # ns_t_dhcid = 49, # Dynamic host configuratin identifier 498 | # ns_t_nsec3 = 50, # Negative security type 3 499 | # ns_t_nsec3param = 51, # Negative security type 3 parameters 500 | # ns_t_hip = 55, # Host Identity Protocol 501 | # ns_t_spf = 99, # Sender Policy Framework 502 | # ns_t_tkey = 249, # Transaction key 503 | # ns_t_tsig = 250, # Transaction signature. 504 | # ns_t_ixfr = 251, # Incremental zone transfer. 505 | # ns_t_axfr = 252, # Transfer zone of authority. 506 | # ns_t_mailb = 253, # Transfer mailbox records. 507 | # ns_t_maila = 254, # Transfer mail agent records. 508 | # ns_t_any = 255, # Wildcard match. 509 | # ns_t_zxfr = 256, # BIND-specific, nonstandard. 510 | # ns_t_dlv = 32769, # DNSSEC look-aside validatation. 511 | # ns_t_max = 65536 512 | 513 | nsRr {.importc: "struct __ns_rr", 514 | header: headers, pure, bycopy, final.} = object 515 | name {.importc: "name".}: array[NS_MAXDNAME, char] 516 | rr_type {.importc: "type".}: uint16 517 | rr_class {.importc: "rr_class".}: uint16 518 | ttl {.importc: "ttl".}: uint16 519 | rdlength {.importc: "rdlength".}: uint16 520 | rdata {.importc: "rdata".}: pointer 521 | 522 | nsMsg {.importc: "struct __ns_msg", 523 | header: headers, pure, bycopy, final.} = object 524 | msg {.importc: "_msg".}: pointer 525 | eom {.importc: "_eom".}: pointer 526 | id {.importc: "_id".}: uint16 527 | flags {.importc: "_flags".}: uint16 528 | counts {.importc: "_counts".}: array[4, uint16] 529 | sections {.importc: "_sections".}: array[4, ptr byte] 530 | sect {.importc: "_sect".}: cint 531 | rrnum {.importc: "_rrnum".}: cint 532 | msgptr {.importc: "_msg_ptr".}: pointer 533 | 534 | proc resInit(state: PResolver): cint 535 | {.importc: "res_ninit", header: headers.} 536 | proc resMakeQuery(state: PResolver, op: cint, dname: cstring, 537 | class: cint, typ: cint, data: pointer, datalen: cint, 538 | newrr: pointer, buf: pointer, buflen: cint): cint 539 | {.importc: "res_nmkquery", header: headers.} 540 | proc nsInitParse(buf: pointer, buflen: cint, pmsg: ptr nsMsg): cint 541 | {.importc: "ns_initparse", header: headers.} 542 | proc nsMsgGetFlag(msg: nsMsg, flag: cint): cint 543 | {.importc: "ns_msg_getflag", header: headers.} 544 | proc nsParseRr(pmsg: ptr nsMsg, section: nsSect, index: cint, 545 | rr: ptr nsRr): cint 546 | {.importc: "ns_parserr", header: headers.} 547 | template nsMsgCount(msg: nsMsg, section: nsSect): uint16 = 548 | msg.counts[cast[int](section)] 549 | template nsRrType(rr: nsRr): nsType = 550 | cast[nsType](rr.rr_type) 551 | 552 | var gResolver {.threadvar.}: PResolver # Global resolver state 553 | 554 | proc newResolver*(): PResolver = 555 | result = PResolver() 556 | discard resInit(result) 557 | 558 | proc getGlobalResolver*(): PResolver = 559 | ## Retrieves the global thread-local DNS resolver. 560 | if gResolver.isNil: gResolver = newResolver() 561 | result = gResolver 562 | 563 | proc asyncGetAddrInfo*(address: string, port: Port, 564 | domain: Domain = nativesockets.AF_INET, 565 | sockType: SockType = nativesockets.SOCK_STREAM, 566 | protocol: Protocol = nativesockets.IPPROTO_TCP): 567 | Future[ptr AsyncAddrInfo] {.async.} = 568 | var blob: pointer = nil 569 | var ai = 0 570 | var nsLastError = noError 571 | var request = newString(PACKETSZ) 572 | var response = newString(PACKETSZ) 573 | 574 | let r = getGlobalResolver() 575 | var qt = if domain == Domain.AF_INET6: 576 | cint(ns_t_aaaa) 577 | else: 578 | cint(ns_t_a) 579 | 580 | let reqLength = resMakeQuery(r, QUERY, address, C_IN, qt, nil, 0, nil, 581 | addr request[0], PACKETSZ) 582 | if reqLength <= 0: 583 | raise newException(ValueError, "Could not create DNS query!") 584 | 585 | let sock = createAsyncNativeSocket(nativesockets.AF_INET, 586 | nativesockets.SOCK_DGRAM, 587 | Protocol.IPPROTO_UDP) 588 | 589 | for i in 0..<3: 590 | if r.nsaddrList[i].sin_family == 0: 591 | break 592 | 593 | var recvAddr = Sockaddr_in() 594 | var recvALen = sizeof(Sockaddr_in).SockLen 595 | 596 | await sendTo(sock, addr request[0], reqLength, 597 | cast[ptr SockAddr](addr r.nsaddrList[i]), 598 | sizeof(Sockaddr_in).SockLen) 599 | var respLength = await recvFromInto(sock, addr response[0], 600 | PACKETSZ, 601 | cast[ptr SockAddr](addr recvAddr), 602 | cast[ptr SockLen](addr recvALen)) 603 | if respLength <= 0: 604 | nsLastError = badDataError 605 | continue 606 | 607 | var msg = nsMsg() 608 | let pres = nsInitParse(cast[pointer](addr response[0]), cint(respLength), 609 | addr msg) 610 | if pres != 0: 611 | nsLastError = parseError 612 | continue 613 | 614 | let gres = cast[nsRcode](nsMsgGetFlag(msg, cast[cint](ns_f_rcode))) 615 | 616 | if gres != ns_r_noerror: 617 | case gres 618 | of ns_r_formerr: nsLastError = formatError 619 | of ns_r_servfail: nsLastError = serverFailError 620 | of ns_r_nxdomain: nsLastError = nxDomainError 621 | of ns_r_notimpl: nsLastError = notimplError 622 | of ns_r_refused: nsLastError = refusedError 623 | else: nsLastError = unknownError 624 | continue 625 | 626 | var count = nsMsgCount(msg, ns_s_an).int 627 | if count <= 0: 628 | nsLastError = zeroError 629 | continue 630 | 631 | let blobsize = sizeof(AddrInfo) * count + sizeof(SockAddr) * count 632 | if isNil(blob): 633 | blob = alloc0(blobsize) 634 | 635 | let p0 = blob 636 | let p1 = cast[pointer](cast[uint](blob) + 637 | cast[uint](sizeof(AddrInfo) * count)) 638 | var addrArr = cast[ptr uncheckedArray[AddrInfo]](p0) 639 | var sockArr = cast[ptr uncheckedArray[SockAddr]](p1) 640 | 641 | for k in 0.. 0 and nsLastError == noError: 697 | result = cast[ptr AsyncAddrInfo](blob) 698 | break 699 | else: 700 | ai = 0 701 | zeroMem(blob, blobsize) 702 | continue 703 | 704 | case nsLastError 705 | of noError: 706 | discard 707 | of formatError: 708 | raise newException(ValueError, "Request format error!") 709 | of serverFailError: 710 | raise newException(ValueError, "DNS Server failure error!") 711 | of nxDomainError: 712 | raise newException(ValueError, "Non existent internet domain error!") 713 | of notimplError: 714 | raise newException(ValueError, "Not implemented error!") 715 | of refusedError: 716 | raise newException(ValueError, "Connection refused error!") 717 | of badDataError: 718 | raise newException(ValueError, "Empty response received!") 719 | of parseError: 720 | raise newException(ValueError, "Response parser error!") 721 | of recordError: 722 | raise newException(ValueError, "Record parser error!") 723 | of unknownError: 724 | raise newException(ValueError, "Unknown error!") 725 | of zeroError: 726 | raise newException(ValueError, "No address records for domain!") 727 | of rtypeError: 728 | raise newException(ValueError, "Wrong record type in response!") 729 | 730 | proc free*(aip: ptr AsyncAddrInfo) = 731 | dealloc(cast[pointer](aip)) 732 | 733 | when isMainModule: 734 | echo "=== synchronous variant" 735 | var saiList = getAddrInfo("www.google.com", Port(80), domain = Domain.AF_INET) 736 | var it = saiList 737 | while not it.isNil: 738 | echo $it 739 | it = it.ai_next 740 | 741 | echo "=== asynchronous variant" 742 | 743 | var aiList = waitFor(asyncGetAddrInfo("www.google.com", Port(80), domain = Domain.AF_INET)) 744 | var ait = aiList 745 | while not ait.isNil: 746 | echo $ait 747 | ait = cast[ptr AsyncAddrInfo](cast[ptr AddrInfo](ait).ai_next) 748 | 749 | if saiList == aiList: 750 | echo "RESULTS EQUAL" 751 | else: 752 | echo "RESULTS NOT EQUAL" 753 | 754 | free(aiList) 755 | freeaddrinfo(saiList) 756 | -------------------------------------------------------------------------------- /asynctools/asyncipc.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Asynchronous tools for Nim Language 4 | # (c) Copyright 2016 Eugene Kabanov 5 | # 6 | # See the file "LICENSE", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## This module implements cross-platform asynchronous inter-process 11 | ## communication. 12 | ## 13 | ## Module uses shared memory for Windows, and fifos for Linux/BSD/MacOS. 14 | ## 15 | ## .. code-block:: nim 16 | ## 17 | ## var inBuffer = newString(64) 18 | ## var outBuffer = "TEST STRING BUFFER" 19 | ## 20 | ## # create new IPC object 21 | ## let ipc = createIpc("test") 22 | ## # open `read` side channel to IPC object 23 | ## let readHandle = open("test", sideReader) 24 | ## # open `write` side channel to IPC object 25 | ## let writeHandle = open("test", sideWriter) 26 | ## 27 | ## # writing string to IPC object 28 | ## waitFor write(writeHandle, cast[pointer](addr outBuffer[0]), len(outBuffer)) 29 | ## # reading data from IPC object 30 | ## var c = waitFor readInto(readHandle, cast[pointer](addr inBuffer[0]), 64) 31 | ## 32 | ## inBuffer.setLen(c) 33 | ## doAssert(inBuffer == outBuffer) 34 | ## 35 | ## # Close `read` side channel 36 | ## close(readHandle) 37 | ## # Close `write` side channel 38 | ## close(writeHandle) 39 | ## # Close IPC object 40 | ## close(ipc) 41 | 42 | import asyncdispatch, os, strutils 43 | 44 | type 45 | SideType* = enum ## Enum represents side of IPC channel (Read or Write) 46 | sideReader, sideWriter 47 | 48 | when defined(nimdoc): 49 | type 50 | AsyncIpc* = object 51 | ## Object represents IPC object. 52 | 53 | AsyncIpcHandle* = object 54 | ## Object represents channel to IPC object. 55 | 56 | proc createIpc*(name: string, size = 65536): AsyncIpc = 57 | ## Creates new ``AsyncIpc`` object with internal buffer size ``size``. 58 | 59 | proc close*(ipc: AsyncIpc) = 60 | ## Closes ``ipc`` object. 61 | 62 | proc open*(name: string, side: SideType, register = true): AsyncIpcHandle = 63 | ## Opens channel with type ``side`` to ``AsyncIpc`` object with name 64 | ## ``name``. 65 | ## 66 | ## If ``register`` is `false`, then created channel will not be registerd 67 | ## with current dispatcher. 68 | 69 | proc close*(ipch: AsyncIpcHandle, unregister = true) = 70 | ## Closes channel to ``AsyncIpc`` object. 71 | ## 72 | ## If ``unregister`` is `false`, channel will not be unregistered from 73 | ## current dispatcher. 74 | 75 | proc write*(ipch: AsyncIpcHandle, data: pointer, size: int): Future[void] = 76 | ## This procedure writes an untyped ``data`` of ``size`` size to the 77 | ## channel ``ipch``. 78 | ## 79 | ## The returned future will complete once ``all`` data has been sent. 80 | 81 | proc readInto*(ipch: AsyncIpcHandle, data: pointer, size: int): Future[int] = 82 | ## This procedure reads **up to** ``size`` bytes from channel ``ipch`` 83 | ## into ``data``, which must at least be of that size. 84 | ## 85 | ## Returned future will complete once all the data requested is read or 86 | ## part of the data has been read. 87 | 88 | proc `$`*(ipc: AsyncIpc): string = 89 | ## Returns string representation of ``ipc`` object. 90 | 91 | proc `$`*(ipch: AsyncIpcHandle): string = 92 | ## Returns string representation of ``ipch`` object. 93 | 94 | elif defined(windows): 95 | import winlean 96 | import sets, hashes # this import only for HackDispatcher 97 | 98 | when not declared(PCustomOverlapped): 99 | type 100 | PCustomOverlapped = CustomRef 101 | 102 | const 103 | mapHeaderName = "asyncipc_" 104 | eventHeaderName = "asyncpipc_" 105 | mapMinSize = 4096 106 | EVENT_MODIFY_STATE = 0x0002.Dword 107 | FILE_MAP_ALL_ACCESS = 0x000F0000 or 0x01 or 0x02 or 0x04 or 0x08 or 0x10 108 | 109 | type 110 | AsyncIpc* = object 111 | handleMap, eventChange: Handle 112 | name: string 113 | size: int32 114 | 115 | AsyncIpcHandle* = object 116 | data: pointer 117 | handleMap, eventChange: Handle 118 | size: int 119 | side: SideType 120 | 121 | CallbackDataImpl = object 122 | ioPort: Handle 123 | handleFd: AsyncFD 124 | waitFd: Handle 125 | ovl: PCustomOverlapped 126 | CallbackData = ptr CallbackDataImpl 127 | 128 | HackDispatcherImpl = object 129 | reserverd: array[56, char] 130 | ioPort: Handle 131 | handles: HashSet[AsyncFD] 132 | HackDispatcher = ptr HackDispatcherImpl 133 | 134 | proc openEvent(dwDesiredAccess: Dword, bInheritHandle: WINBOOL, 135 | lpName: WideCString): Handle 136 | {.importc: "OpenEventW", stdcall, dynlib: "kernel32".} 137 | proc openFileMapping(dwDesiredAccess: Dword, bInheritHandle: Winbool, 138 | lpName: WideCString): Handle 139 | {.importc: "OpenFileMappingW", stdcall, dynlib: "kernel32".} 140 | proc interlockedOr(a: ptr int32, b: int32) 141 | {.importc: "_InterlockedOr", header: "intrin.h".} 142 | proc interlockedAnd(a: ptr int32, b: int32) 143 | {.importc: "_InterlockedAnd", header: "intrin.h".} 144 | 145 | proc getCurrentDispatcher(): auto = 146 | when defined(nimv2): 147 | # New runtime will run into segfault if using HackDispatcher, but old versions of Nim require 148 | # HackDispatcher so we can access some internal fields. 149 | # If using the new runtime then likely using a new enough Nim version that the needed fields 150 | # are exported 151 | getGlobalDispatcher() 152 | else: 153 | cast[HackDispatcher](getGlobalDispatcher()) 154 | 155 | template getIoHandler(p: HackDispatcher): Handle = 156 | ## Returns the IO handle for a dispatcher. 157 | p.ioPort 158 | 159 | proc `$`*(ipc: AsyncIpc): string = 160 | if ipc.handleMap == Handle(0): 161 | result = "AsyncIpc [invalid or inactive handle]" 162 | else: 163 | var data = mapViewOfFileEx(ipc.handleMap, FILE_MAP_READ, 0, 0, mapMinSize, 164 | nil) 165 | if data == nil: 166 | result = "AsyncIpc [invalid or inactive handle]" 167 | else: 168 | var status = "" 169 | var stat = cast[ptr int32](data)[] 170 | if (stat and 1) != 0: status &= "R" 171 | if (stat and 2) != 0: status &= "W" 172 | discard unmapViewOfFile(data) 173 | result = "AsyncIpc [handle = 0x" & toHex(int(ipc.handleMap)) & ", " & 174 | "event = 0x" & toHex(int(ipc.eventChange)) & ", " & 175 | "name = \"" & ipc.name & "\", " & 176 | "size = " & $ipc.size & ", " & 177 | "status = [" & status & "]" & 178 | "]" 179 | 180 | proc `$`*(ipch: AsyncIpcHandle): string = 181 | var side = if ipch.side == sideWriter: "writer" else: "reader" 182 | result = "AsyncIpcHandle [handle = 0x" & toHex(int(ipch.handleMap)) & ", " & 183 | "event = 0x" & toHex(int(ipch.eventChange)) & ", " & 184 | "data = 0x" & toHex(cast[int](ipch.data)) & ", " & 185 | "size = " & $ipch.size & ", " & 186 | "side = " & side & 187 | "]" 188 | 189 | proc createIpc*(name: string, size = 65536): AsyncIpc = 190 | var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint, 191 | lpSecurityDescriptor: nil, bInheritHandle: 1) 192 | let mapName = newWideCString(mapHeaderName & name) 193 | let nameChange = newWideCString(eventHeaderName & name & "_change") 194 | let mapSize = size + mapMinSize 195 | 196 | doAssert(size > mapMinSize) 197 | let handleMap = createFileMappingW(INVALID_HANDLE_VALUE, 198 | cast[pointer](addr sa), 199 | PAGE_READWRITE, 0, mapSize.Dword, 200 | cast[pointer](WideCString(mapName))) 201 | if handleMap == 0: 202 | raiseOSError(osLastError()) 203 | var eventChange = createEvent(addr sa, 0, 0, addr nameChange[0]) 204 | if eventChange == 0: 205 | let err = osLastError() 206 | discard closeHandle(handleMap) 207 | raiseOSError(err) 208 | 209 | var data = mapViewOfFileEx(handleMap, FILE_MAP_WRITE, 0, 0, mapMinSize, nil) 210 | if data == nil: 211 | let err = osLastError() 212 | discard closeHandle(handleMap) 213 | discard closeHandle(eventChange) 214 | raiseOSError(err) 215 | 216 | cast[ptr int32](cast[uint](data) + sizeof(int32).uint * 2)[] = size.int32 217 | 218 | result = AsyncIpc( 219 | name: name, 220 | handleMap: handleMap, 221 | size: size.int32, 222 | eventChange: eventChange 223 | ) 224 | 225 | proc close*(ipc: AsyncIpc) = 226 | if closeHandle(ipc.handleMap) == 0: 227 | raiseOSError(osLastError()) 228 | if closeHandle(ipc.eventChange) == 0: 229 | raiseOSError(osLastError()) 230 | 231 | proc open*(name: string, side: SideType, register = true): AsyncIpcHandle = 232 | let mapName = newWideCString(mapHeaderName & name) 233 | let nameChange = newWideCString(eventHeaderName & name & "_change") 234 | 235 | var handleMap = openFileMapping(FILE_MAP_ALL_ACCESS, 1, mapName) 236 | if handleMap == 0: 237 | raiseOSError(osLastError()) 238 | 239 | var eventChange = openEvent(EVENT_MODIFY_STATE or SYNCHRONIZE, 240 | 0, nameChange) 241 | if eventChange == 0: 242 | let err = osLastError() 243 | discard closeHandle(handleMap) 244 | raiseOSError(err) 245 | 246 | var data = mapViewOfFileEx(handleMap, FILE_MAP_WRITE, 0, 0, mapMinSize, nil) 247 | if data == nil: 248 | let err = osLastError() 249 | discard closeHandle(handleMap) 250 | discard closeHandle(eventChange) 251 | raiseOSError(err) 252 | 253 | var size = cast[ptr int32](cast[uint](data) + sizeof(int32).uint * 2)[] 254 | doAssert(size > mapMinSize) 255 | 256 | if unmapViewOfFile(data) == 0: 257 | let err = osLastError() 258 | discard closeHandle(handleMap) 259 | discard closeHandle(eventChange) 260 | raiseOSError(err) 261 | 262 | when declared(WinSizeT): 263 | let sizeB = size.WinSizeT 264 | else: 265 | let sizeB = size 266 | 267 | data = mapViewOfFileEx(handleMap, FILE_MAP_WRITE, 0, 0, sizeB, nil) 268 | if data == nil: 269 | let err = osLastError() 270 | discard closeHandle(handleMap) 271 | discard closeHandle(eventChange) 272 | raiseOSError(err) 273 | 274 | if side == sideWriter: 275 | interlockedOr(cast[ptr int32](data), 2) 276 | else: 277 | interlockedOr(cast[ptr int32](data), 1) 278 | 279 | if register: 280 | let p = getCurrentDispatcher() 281 | p.handles.incl(AsyncFD(eventChange)) 282 | 283 | result = AsyncIpcHandle( 284 | data: data, 285 | size: size, 286 | handleMap: handleMap, 287 | eventChange: eventChange, 288 | side: side 289 | ) 290 | 291 | proc close*(ipch: AsyncIpcHandle, unregister = true) = 292 | if ipch.side == sideWriter: 293 | interlockedAnd(cast[ptr int32](ipch.data), not(2)) 294 | else: 295 | interlockedAnd(cast[ptr int32](ipch.data), not(1)) 296 | 297 | if unregister: 298 | let p = getCurrentDispatcher() 299 | p.handles.excl(AsyncFD(ipch.eventChange)) 300 | 301 | if unmapViewOfFile(ipch.data) == 0: 302 | raiseOSError(osLastError()) 303 | if closeHandle(ipch.eventChange) == 0: 304 | raiseOSError(osLastError()) 305 | if closeHandle(ipch.handleMap) == 0: 306 | raiseOSError(osLastError()) 307 | 308 | template getSize(ipch: AsyncIpcHandle): int32 = 309 | cast[ptr int32](cast[uint](ipch.data) + sizeof(int32).uint)[] 310 | 311 | template getPointer(ipch: AsyncIpcHandle): pointer = 312 | cast[pointer](cast[uint](ipch.data) + sizeof(int32).uint * 3) 313 | 314 | template setSize(ipc: AsyncIpcHandle, size: int) = 315 | cast[ptr int32](cast[uint](ipc.data) + sizeof(int32).uint)[] = size.int32 316 | 317 | template setData(ipc: AsyncIpcHandle, data: pointer, size: int) = 318 | copyMem(getPointer(ipc), data, size) 319 | 320 | template getData(ipc: AsyncIpcHandle, data: pointer, size: int) = 321 | copyMem(data, getPointer(ipc), size) 322 | 323 | {.push stackTrace:off.} 324 | proc waitableCallback(param: pointer, 325 | timerOrWaitFired: WINBOOL): void {.stdcall.} = 326 | var p = cast[CallbackData](param) 327 | discard postQueuedCompletionStatus(p.ioPort, timerOrWaitFired.Dword, 328 | ULONG_PTR(p.handleFd), 329 | cast[pointer](p.ovl)) 330 | {.pop.} 331 | 332 | template registerWaitableChange(ipc: AsyncIpcHandle, pcd, handleCallback) = 333 | let p = getCurrentDispatcher() 334 | var flags = (WT_EXECUTEINWAITTHREAD or WT_EXECUTEONLYONCE).Dword 335 | pcd.ioPort = p.getIoHandler() 336 | pcd.handleFd = AsyncFD(ipc.eventChange) 337 | var ol = PCustomOverlapped() 338 | GC_ref(ol) 339 | ol.data = CompletionData(fd: AsyncFD(ipc.eventChange), cb: handleCallback) 340 | # We need to protect our callback environment value, so GC will not free it 341 | # accidentally. 342 | ol.data.cell = system.protect(rawEnv(ol.data.cb)) 343 | pcd.ovl = ol 344 | if not registerWaitForSingleObject(addr(pcd.waitFd), ipc.eventChange, 345 | cast[WAITORTIMERCALLBACK](waitableCallback), 346 | cast[pointer](pcd), INFINITE, flags): 347 | GC_unref(ol) 348 | deallocShared(cast[pointer](pcd)) 349 | raiseOSError(osLastError()) 350 | 351 | proc write*(ipch: AsyncIpcHandle, data: pointer, size: int): Future[void] = 352 | var retFuture = newFuture[void]("asyncipc.write") 353 | doAssert(ipch.size >= size and size > 0) 354 | doAssert(ipch.side == sideWriter) 355 | 356 | if getSize(ipch) == 0: 357 | setData(ipch, data, size) 358 | setSize(ipch, size) 359 | if setEvent(ipch.eventChange) == 0: 360 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 361 | else: 362 | retFuture.complete() 363 | else: 364 | var pcd = cast[CallbackData](allocShared0(sizeof(CallbackDataImpl))) 365 | 366 | proc writecb(fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) = 367 | # unregistering wait handle and free `CallbackData` 368 | if unregisterWait(pcd.waitFd) == 0: 369 | let err = osLastError() 370 | if err.int32 != ERROR_IO_PENDING: 371 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 372 | deallocShared(cast[pointer](pcd)) 373 | 374 | if not retFuture.finished: 375 | if errcode == OSErrorCode(-1): 376 | setData(ipch, data, size) 377 | setSize(ipch, size) 378 | if setEvent(ipch.eventChange) == 0: 379 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 380 | else: 381 | retFuture.complete() 382 | else: 383 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 384 | 385 | registerWaitableChange(ipch, pcd, writecb) 386 | 387 | return retFuture 388 | 389 | proc readInto*(ipch: AsyncIpcHandle, data: pointer, size: int): Future[int] = 390 | var retFuture = newFuture[int]("asyncipc.readInto") 391 | doAssert(size > 0) 392 | doAssert(ipch.side == sideReader) 393 | 394 | var packetSize = getSize(ipch) 395 | if packetSize == 0: 396 | var pcd = cast[CallbackData](allocShared0(sizeof(CallbackDataImpl))) 397 | 398 | proc readcb(fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) = 399 | # unregistering wait handle and free `CallbackData` 400 | if unregisterWait(pcd.waitFd) == 0: 401 | let err = osLastError() 402 | if err.int32 != ERROR_IO_PENDING: 403 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 404 | deallocShared(cast[pointer](pcd)) 405 | 406 | if not retFuture.finished: 407 | if errcode == OSErrorCode(-1): 408 | packetSize = getSize(ipch) 409 | if packetSize > 0: 410 | getData(ipch, data, packetSize) 411 | setSize(ipch, 0) 412 | if setEvent(ipch.eventChange) == 0: 413 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 414 | else: 415 | retFuture.complete(packetSize) 416 | else: 417 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 418 | 419 | registerWaitableChange(ipch, pcd, readcb) 420 | else: 421 | if size < packetSize: 422 | packetSize = size.int32 423 | getData(ipch, data, packetSize) 424 | setSize(ipch, 0) 425 | if setEvent(ipch.eventChange) == 0: 426 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 427 | else: 428 | retFuture.complete(packetSize) 429 | 430 | return retFuture 431 | else: 432 | import posix 433 | 434 | const 435 | pipeHeaderName = r"/tmp/asyncipc_" 436 | 437 | type 438 | AsyncIpc* = object 439 | name: string 440 | size: int 441 | 442 | type 443 | AsyncIpcHandle* = object 444 | fd: AsyncFD 445 | side: SideType 446 | 447 | proc `$`*(ipc: AsyncIpc): string = 448 | let ipcName = pipeHeaderName & ipc.name 449 | if posix.access(cstring(ipcName), F_OK) != 0: 450 | result = "AsyncIpc [invalid or inactive handle]" 451 | else: 452 | result = "AsyncIpc [name = \"" & ipc.name & "\", " & 453 | "size = " & $ipc.size & 454 | "]" 455 | 456 | proc `$`*(ipch: AsyncIpcHandle): string = 457 | let side = if ipch.side == sideWriter: "writer" else: "reader" 458 | result = "AsyncIpcHandle [fd = 0x" & toHex(cint(ipch.fd)) & ", " & 459 | "side = " & side & 460 | "]" 461 | 462 | proc setNonBlocking(fd: cint) {.inline.} = 463 | var x = fcntl(fd, F_GETFL, 0) 464 | if x == -1: 465 | raiseOSError(osLastError()) 466 | else: 467 | var mode = x or O_NONBLOCK 468 | if fcntl(fd, F_SETFL, mode) == -1: 469 | raiseOSError(osLastError()) 470 | 471 | proc createIpc*(name: string, size = 65536): AsyncIpc = 472 | let pipeName = pipeHeaderName & name 473 | if mkfifo(cstring(pipeName), Mode(0x1B6)) != 0: 474 | raiseOSError(osLastError()) 475 | result = AsyncIpc( 476 | name: name, 477 | size: size 478 | ) 479 | 480 | proc close*(ipc: AsyncIpc) = 481 | let pipeName = pipeHeaderName & ipc.name 482 | if posix.unlink(cstring(pipeName)) != 0: 483 | raiseOSError(osLastError()) 484 | 485 | proc open*(name: string, side: SideType, register = true): AsyncIpcHandle = 486 | var pipeFd: cint = 0 487 | let pipeName = pipeHeaderName & name 488 | 489 | if side == sideReader: 490 | pipeFd = open(pipeName, O_NONBLOCK or O_RDWR) 491 | else: 492 | pipeFd = open(pipeName, O_NONBLOCK or O_WRONLY) 493 | 494 | if pipeFd < 0: 495 | raiseOSError(osLastError()) 496 | 497 | let afd = AsyncFD(pipeFd) 498 | if register: 499 | register(afd) 500 | 501 | result = AsyncIpcHandle( 502 | fd: afd, 503 | side: side, 504 | ) 505 | 506 | proc close*(ipch: AsyncIpcHandle) = 507 | if close(cint(ipch.fd)) != 0: 508 | raiseOSError(osLastError()) 509 | 510 | proc write*(ipch: AsyncIpcHandle, data: pointer, nbytes: int): Future[void] = 511 | var retFuture = newFuture[void]("asyncipc.write") 512 | var written = 0 513 | 514 | proc cb(fd: AsyncFD): bool = 515 | result = true 516 | let reminder = nbytes - written 517 | let pdata = cast[pointer](cast[uint](data) + written.uint) 518 | let res = posix.write(cint(ipch.fd), pdata, cint(reminder)) 519 | if res < 0: 520 | let err = osLastError() 521 | if err.int32 != EAGAIN: 522 | retFuture.fail(newException(OSError, osErrorMsg(err))) 523 | else: 524 | result = false # We still want this callback to be called. 525 | else: 526 | written.inc(res) 527 | if res != reminder: 528 | result = false 529 | else: 530 | retFuture.complete() 531 | 532 | doAssert(ipch.side == sideWriter) 533 | 534 | if not cb(ipch.fd): 535 | addWrite(ipch.fd, cb) 536 | 537 | return retFuture 538 | 539 | proc readInto*(ipch: AsyncIpcHandle, data: pointer, 540 | nbytes: int): Future[int] = 541 | var retFuture = newFuture[int]("asyncipc.readInto") 542 | proc cb(fd: AsyncFD): bool = 543 | result = true 544 | let res = posix.read(cint(ipch.fd), data, cint(nbytes)) 545 | if res < 0: 546 | let lastError = osLastError() 547 | if lastError.int32 != EAGAIN: 548 | retFuture.fail(newException(OSError, osErrorMsg(lastError))) 549 | else: 550 | result = false # We still want this callback to be called. 551 | elif res == 0: 552 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 553 | else: 554 | retFuture.complete(res) 555 | 556 | doAssert(ipch.side == sideReader) 557 | 558 | if not cb(ipch.fd): 559 | addRead(ipch.fd, cb) 560 | return retFuture 561 | 562 | when isMainModule: 563 | when not defined(windows): 564 | discard posix.unlink(pipeHeaderName & "test") 565 | 566 | var inBuffer = newString(64) 567 | var outBuffer = "TEST STRING BUFFER" 568 | let ipc = createIpc("test") 569 | let readHandle = open("test", sideReader) 570 | let writeHandle = open("test", sideWriter) 571 | 572 | waitFor write(writeHandle, cast[pointer](addr outBuffer[0]), len(outBuffer)) 573 | var c = waitFor readInto(readHandle, cast[pointer](addr inBuffer[0]), 64) 574 | inBuffer.setLen(c) 575 | doAssert(inBuffer == outBuffer) 576 | close(readHandle) 577 | close(writeHandle) 578 | close(ipc) 579 | -------------------------------------------------------------------------------- /asynctools/asyncpipe.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Asynchronous tools for Nim Language 4 | # (c) Copyright 2016 Eugene Kabanov 5 | # 6 | # See the file "LICENSE", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## This module implements cross-platform asynchronous pipes communication. 11 | ## 12 | ## Module uses named pipes for Windows, and anonymous pipes for 13 | ## Linux/BSD/MacOS. 14 | ## 15 | ## .. code-block:: nim 16 | ## var inBuffer = newString(64) 17 | ## var outBuffer = "TEST STRING BUFFER" 18 | ## 19 | ## # Create new pipe 20 | ## var o = createPipe() 21 | ## 22 | ## # Write string to pipe 23 | ## waitFor write(o, cast[pointer](addr outBuffer[0]), outBuffer.len) 24 | ## 25 | ## # Read data from pipe 26 | ## var c = waitFor readInto(o, cast[pointer](addr inBuffer[0]), inBuffer.len) 27 | ## 28 | ## inBuffer.setLen(c) 29 | ## doAssert(inBuffer == outBuffer) 30 | ## 31 | ## # Close pipe 32 | ## close(o) 33 | 34 | import asyncdispatch, os 35 | 36 | when defined(nimdoc): 37 | type 38 | AsyncPipe* = ref object ## Object represents ``AsyncPipe``. 39 | 40 | proc createPipe*(register = true): AsyncPipe = 41 | ## Create descriptor pair for interprocess communication. 42 | ## 43 | ## Returns ``AsyncPipe`` object, which represents OS specific pipe. 44 | ## 45 | ## If ``register`` is `false`, both pipes will not be registered with 46 | ## current dispatcher. 47 | 48 | proc closeRead*(pipe: AsyncPipe, unregister = true) = 49 | ## Closes read side of pipe ``pipe``. 50 | ## 51 | ## If ``unregister`` is `false`, pipe will not be unregistered from 52 | ## current dispatcher. 53 | 54 | proc closeWrite*(pipe: AsyncPipe, unregister = true) = 55 | ## Closes write side of pipe ``pipe``. 56 | ## 57 | ## If ``unregister`` is `false`, pipe will not be unregistered from 58 | ## current dispatcher. 59 | 60 | proc getReadHandle*(pipe: AsyncPipe): int = 61 | ## Returns OS specific handle for read side of pipe ``pipe``. 62 | 63 | proc getWriteHandle*(pipe: AsyncPipe): int = 64 | ## Returns OS specific handle for write side of pipe ``pipe``. 65 | 66 | proc getHandles*(pipe: AsyncPipe): array[2, Handle] = 67 | ## Returns OS specific handles of ``pipe``. 68 | 69 | proc getHandles*(pipe: AsyncPipe): array[2, cint] = 70 | ## Returns OS specific handles of ``pipe``. 71 | 72 | proc close*(pipe: AsyncPipe, unregister = true) = 73 | ## Closes both ends of pipe ``pipe``. 74 | ## 75 | ## If ``unregister`` is `false`, pipe will not be unregistered from 76 | ## current dispatcher. 77 | 78 | proc write*(pipe: AsyncPipe, data: pointer, nbytes: int): Future[int] = 79 | ## This procedure writes an untyped ``data`` of ``size`` size to the 80 | ## pipe ``pipe``. 81 | ## 82 | ## The returned future will complete once ``all`` data has been sent or 83 | ## part of the data has been sent. 84 | 85 | proc readInto*(pipe: AsyncPipe, data: pointer, nbytes: int): Future[int] = 86 | ## This procedure reads up to ``size`` bytes from pipe ``pipe`` 87 | ## into ``data``, which must at least be of that size. 88 | ## 89 | ## Returned future will complete once all the data requested is read or 90 | ## part of the data has been read. 91 | 92 | proc asyncWrap*(readHandle: Handle|cint = 0, 93 | writeHandle: Handle|cint = 0): AsyncPipe = 94 | ## Wraps existing OS specific pipe handles to ``AsyncPipe`` and register 95 | ## it with current dispatcher. 96 | ## 97 | ## ``readHandle`` - read side of pipe (optional value). 98 | ## ``writeHandle`` - write side of pipe (optional value). 99 | ## **Note**: At least one handle must be specified. 100 | ## 101 | ## Returns ``AsyncPipe`` object. 102 | ## 103 | ## Windows handles must be named pipes created with ``CreateNamedPipe`` and 104 | ## ``FILE_FLAG_OVERLAPPED`` in flags. You can use ``ReopenFile()`` function 105 | ## to convert existing handle to overlapped variant. 106 | ## 107 | ## Posix handle will be modified with ``O_NONBLOCK``. 108 | 109 | proc asyncUnwrap*(pipe: AsyncPipe) = 110 | ## Unregisters ``pipe`` handle from current async dispatcher. 111 | 112 | proc `$`*(pipe: AsyncPipe) = 113 | ## Returns string representation of ``AsyncPipe`` object. 114 | 115 | else: 116 | 117 | when defined(windows): 118 | import winlean 119 | else: 120 | import posix 121 | 122 | type 123 | AsyncPipe* = ref object of RootRef 124 | when defined(windows): 125 | readPipe: Handle 126 | writePipe: Handle 127 | else: 128 | readPipe: cint 129 | writePipe: cint 130 | 131 | when defined(windows): 132 | 133 | proc QueryPerformanceCounter(res: var int64) 134 | {.importc: "QueryPerformanceCounter", stdcall, dynlib: "kernel32".} 135 | proc connectNamedPipe(hNamedPipe: Handle, lpOverlapped: pointer): WINBOOL 136 | {.importc: "ConnectNamedPipe", stdcall, dynlib: "kernel32".} 137 | 138 | when not declared(PCustomOverlapped): 139 | type 140 | PCustomOverlapped = CustomRef 141 | 142 | const 143 | pipeHeaderName = r"\\.\pipe\asyncpipe_" 144 | 145 | const 146 | DEFAULT_PIPE_SIZE = 65536'i32 147 | FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000'i32 148 | PIPE_WAIT = 0x00000000'i32 149 | PIPE_TYPE_BYTE = 0x00000000'i32 150 | PIPE_READMODE_BYTE = 0x00000000'i32 151 | ERROR_PIPE_CONNECTED = 535 152 | ERROR_PIPE_BUSY = 231 153 | ERROR_BROKEN_PIPE = 109 154 | ERROR_PIPE_NOT_CONNECTED = 233 155 | 156 | proc `$`*(pipe: AsyncPipe): string = 157 | result = "AsyncPipe [read = " & $(cast[uint](pipe.readPipe)) & 158 | ", write = " & $(cast[int](pipe.writePipe)) & "]" 159 | 160 | proc createPipe*(register = true): AsyncPipe = 161 | 162 | var number = 0'i64 163 | var pipeName: WideCString 164 | var pipeIn: Handle 165 | var pipeOut: Handle 166 | var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint, 167 | lpSecurityDescriptor: nil, bInheritHandle: 1) 168 | while true: 169 | QueryPerformanceCounter(number) 170 | let p = pipeHeaderName & $number 171 | pipeName = newWideCString(p) 172 | var openMode = FILE_FLAG_FIRST_PIPE_INSTANCE or FILE_FLAG_OVERLAPPED or 173 | PIPE_ACCESS_INBOUND 174 | var pipeMode = PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT 175 | pipeIn = createNamedPipe(pipeName, openMode, pipeMode, 1'i32, 176 | DEFAULT_PIPE_SIZE, DEFAULT_PIPE_SIZE, 177 | 1'i32, addr sa) 178 | if pipeIn == INVALID_HANDLE_VALUE: 179 | let err = osLastError() 180 | if err.int32 != ERROR_PIPE_BUSY: 181 | raiseOsError(err) 182 | else: 183 | break 184 | 185 | var openMode = (FILE_WRITE_DATA or SYNCHRONIZE) 186 | pipeOut = createFileW(pipeName, openMode, 0, addr(sa), OPEN_EXISTING, 187 | FILE_FLAG_OVERLAPPED, 0) 188 | if pipeOut == INVALID_HANDLE_VALUE: 189 | let err = osLastError() 190 | discard closeHandle(pipeIn) 191 | raiseOsError(err) 192 | 193 | result = AsyncPipe(readPipe: pipeIn, writePipe: pipeOut) 194 | 195 | var ovl = OVERLAPPED() 196 | let res = connectNamedPipe(pipeIn, cast[pointer](addr ovl)) 197 | if res == 0: 198 | let err = osLastError() 199 | if err.int32 == ERROR_PIPE_CONNECTED: 200 | discard 201 | elif err.int32 == ERROR_IO_PENDING: 202 | var bytesRead = 0.Dword 203 | if getOverlappedResult(pipeIn, addr ovl, bytesRead, 1) == 0: 204 | let oerr = osLastError() 205 | discard closeHandle(pipeIn) 206 | discard closeHandle(pipeOut) 207 | raiseOsError(oerr) 208 | else: 209 | discard closeHandle(pipeIn) 210 | discard closeHandle(pipeOut) 211 | raiseOsError(err) 212 | 213 | if register: 214 | register(AsyncFD(pipeIn)) 215 | register(AsyncFD(pipeOut)) 216 | 217 | proc asyncWrap*(readHandle = Handle(0), 218 | writeHandle = Handle(0)): AsyncPipe = 219 | doAssert(readHandle != 0 or writeHandle != 0) 220 | 221 | result = AsyncPipe(readPipe: readHandle, writePipe: writeHandle) 222 | if result.readPipe != 0: 223 | register(AsyncFD(result.readPipe)) 224 | if result.writePipe != 0: 225 | register(AsyncFD(result.writePipe)) 226 | 227 | proc asyncUnwrap*(pipe: AsyncPipe) = 228 | if pipe.readPipe != 0: 229 | unregister(AsyncFD(pipe.readPipe)) 230 | if pipe.writePipe != 0: 231 | unregister(AsyncFD(pipe.writePipe)) 232 | 233 | proc getReadHandle*(pipe: AsyncPipe): Handle {.inline.} = 234 | result = pipe.readPipe 235 | 236 | proc getWriteHandle*(pipe: AsyncPipe): Handle {.inline.} = 237 | result = pipe.writePipe 238 | 239 | proc getHandles*(pipe: AsyncPipe): array[2, Handle] {.inline.} = 240 | result = [pipe.readPipe, pipe.writePipe] 241 | 242 | proc closeRead*(pipe: AsyncPipe, unregister = true) = 243 | if pipe.readPipe != 0: 244 | if unregister: 245 | unregister(AsyncFD(pipe.readPipe)) 246 | if closeHandle(pipe.readPipe) == 0: 247 | raiseOsError(osLastError()) 248 | pipe.readPipe = 0 249 | 250 | proc closeWrite*(pipe: AsyncPipe, unregister = true) = 251 | if pipe.writePipe != 0: 252 | if unregister: 253 | unregister(AsyncFD(pipe.writePipe)) 254 | if closeHandle(pipe.writePipe) == 0: 255 | raiseOsError(osLastError()) 256 | pipe.writePipe = 0 257 | 258 | proc close*(pipe: AsyncPipe, unregister = true) = 259 | closeRead(pipe, unregister) 260 | closeWrite(pipe, unregister) 261 | 262 | proc write*(pipe: AsyncPipe, data: pointer, nbytes: int): Future[int] = 263 | var retFuture = newFuture[int]("asyncpipe.write") 264 | var ol = PCustomOverlapped() 265 | 266 | if pipe.writePipe == 0: 267 | retFuture.fail(newException(ValueError, 268 | "Write side of pipe closed or not available")) 269 | else: 270 | GC_ref(ol) 271 | ol.data = CompletionData(fd: AsyncFD(pipe.writePipe), cb: 272 | proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) = 273 | if not retFuture.finished: 274 | if errcode == OSErrorCode(-1): 275 | retFuture.complete(bytesCount) 276 | else: 277 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 278 | ) 279 | let res = writeFile(pipe.writePipe, data, nbytes.int32, nil, 280 | cast[POVERLAPPED](ol)).bool 281 | if not res: 282 | let errcode = osLastError() 283 | if errcode.int32 != ERROR_IO_PENDING: 284 | GC_unref(ol) 285 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 286 | return retFuture 287 | 288 | proc readInto*(pipe: AsyncPipe, data: pointer, nbytes: int): Future[int] = 289 | var retFuture = newFuture[int]("asyncpipe.readInto") 290 | var ol = PCustomOverlapped() 291 | 292 | if pipe.readPipe == 0: 293 | retFuture.fail(newException(ValueError, 294 | "Read side of pipe closed or not available")) 295 | else: 296 | GC_ref(ol) 297 | ol.data = CompletionData(fd: AsyncFD(pipe.readPipe), cb: 298 | proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) = 299 | if not retFuture.finished: 300 | if errcode == OSErrorCode(-1): 301 | assert(bytesCount > 0 and bytesCount <= nbytes.int32) 302 | retFuture.complete(bytesCount) 303 | else: 304 | if errcode.int32 in {ERROR_BROKEN_PIPE, 305 | ERROR_PIPE_NOT_CONNECTED}: 306 | retFuture.complete(bytesCount) 307 | else: 308 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 309 | ) 310 | let res = readFile(pipe.readPipe, data, nbytes.int32, nil, 311 | cast[POVERLAPPED](ol)).bool 312 | if not res: 313 | let err = osLastError() 314 | if err.int32 in {ERROR_BROKEN_PIPE, ERROR_PIPE_NOT_CONNECTED}: 315 | GC_unref(ol) 316 | retFuture.complete(0) 317 | elif err.int32 != ERROR_IO_PENDING: 318 | GC_unref(ol) 319 | retFuture.fail(newException(OSError, osErrorMsg(err))) 320 | return retFuture 321 | else: 322 | 323 | proc setNonBlocking(fd: cint) {.inline.} = 324 | var x = fcntl(fd, F_GETFL, 0) 325 | if x == -1: 326 | raiseOSError(osLastError()) 327 | else: 328 | var mode = x or O_NONBLOCK 329 | if fcntl(fd, F_SETFL, mode) == -1: 330 | raiseOSError(osLastError()) 331 | 332 | proc `$`*(pipe: AsyncPipe): string = 333 | result = "AsyncPipe [read = " & $(cast[uint](pipe.readPipe)) & 334 | ", write = " & $(cast[uint](pipe.writePipe)) & "]" 335 | 336 | proc createPipe*(size = 65536, register = true): AsyncPipe = 337 | var fds: array[2, cint] 338 | if posix.pipe(fds) == -1: 339 | raiseOSError(osLastError()) 340 | setNonBlocking(fds[0]) 341 | setNonBlocking(fds[1]) 342 | 343 | result = AsyncPipe(readPipe: fds[0], writePipe: fds[1]) 344 | 345 | if register: 346 | register(AsyncFD(fds[0])) 347 | register(AsyncFD(fds[1])) 348 | 349 | proc asyncWrap*(readHandle = cint(0), writeHandle = cint(0)): AsyncPipe = 350 | doAssert((readHandle != 0) or (writeHandle != 0)) 351 | result = AsyncPipe(readPipe: readHandle, writePipe: writeHandle) 352 | if result.readPipe != 0: 353 | setNonBlocking(result.readPipe) 354 | register(AsyncFD(result.readPipe)) 355 | if result.writePipe != 0: 356 | setNonBlocking(result.writePipe) 357 | register(AsyncFD(result.writePipe)) 358 | 359 | proc asyncUnwrap*(pipe: AsyncPipe) = 360 | if pipe.readPipe != 0: 361 | unregister(AsyncFD(pipe.readPipe)) 362 | if pipe.writePipe != 0: 363 | unregister(AsyncFD(pipe.writePipe)) 364 | 365 | proc getReadHandle*(pipe: AsyncPipe): cint {.inline.} = 366 | result = pipe.readPipe 367 | 368 | proc getWriteHandle*(pipe: AsyncPipe): cint {.inline.} = 369 | result = pipe.writePipe 370 | 371 | proc getHandles*(pipe: AsyncPipe): array[2, cint] {.inline.} = 372 | result = [pipe.readPipe, pipe.writePipe] 373 | 374 | proc closeRead*(pipe: AsyncPipe, unregister = true) = 375 | if pipe.readPipe != 0: 376 | if unregister: 377 | unregister(AsyncFD(pipe.readPipe)) 378 | if posix.close(cint(pipe.readPipe)) != 0: 379 | raiseOSError(osLastError()) 380 | pipe.readPipe = 0 381 | 382 | proc closeWrite*(pipe: AsyncPipe, unregister = true) = 383 | if pipe.writePipe != 0: 384 | if unregister: 385 | unregister(AsyncFD(pipe.writePipe)) 386 | if posix.close(cint(pipe.writePipe)) != 0: 387 | raiseOSError(osLastError()) 388 | pipe.writePipe = 0 389 | 390 | proc close*(pipe: AsyncPipe, unregister = true) = 391 | closeRead(pipe, unregister) 392 | closeWrite(pipe, unregister) 393 | 394 | proc write*(pipe: AsyncPipe, data: pointer, nbytes: int): Future[int] = 395 | var retFuture = newFuture[int]("asyncpipe.write") 396 | var bytesWrote = 0 397 | 398 | proc cb(fd: AsyncFD): bool = 399 | result = true 400 | let reminder = nbytes - bytesWrote 401 | let pdata = cast[pointer](cast[uint](data) + bytesWrote.uint) 402 | let res = posix.write(pipe.writePipe, pdata, cint(reminder)) 403 | if res < 0: 404 | let err = osLastError() 405 | if err.int32 != EAGAIN: 406 | retFuture.fail(newException(OSError, osErrorMsg(err))) 407 | else: 408 | result = false # We still want this callback to be called. 409 | elif res == 0: 410 | retFuture.complete(bytesWrote) 411 | else: 412 | bytesWrote.inc(res) 413 | if res != reminder: 414 | result = false 415 | else: 416 | retFuture.complete(bytesWrote) 417 | 418 | if pipe.writePipe == 0: 419 | retFuture.fail(newException(ValueError, 420 | "Write side of pipe closed or not available")) 421 | else: 422 | if not cb(AsyncFD(pipe.writePipe)): 423 | addWrite(AsyncFD(pipe.writePipe), cb) 424 | return retFuture 425 | 426 | proc readInto*(pipe: AsyncPipe, data: pointer, nbytes: int): Future[int] = 427 | var retFuture = newFuture[int]("asyncpipe.readInto") 428 | proc cb(fd: AsyncFD): bool = 429 | result = true 430 | let res = posix.read(pipe.readPipe, data, cint(nbytes)) 431 | if res < 0: 432 | let err = osLastError() 433 | if err.int32 != EAGAIN: 434 | retFuture.fail(newException(OSError, osErrorMsg(err))) 435 | else: 436 | result = false # We still want this callback to be called. 437 | elif res == 0: 438 | retFuture.complete(0) 439 | else: 440 | retFuture.complete(res) 441 | 442 | if pipe.readPipe == 0: 443 | retFuture.fail(newException(ValueError, 444 | "Read side of pipe closed or not available")) 445 | else: 446 | if not cb(AsyncFD(pipe.readPipe)): 447 | addRead(AsyncFD(pipe.readPipe), cb) 448 | return retFuture 449 | 450 | when isMainModule: 451 | 452 | when not defined(windows): 453 | const 454 | SIG_DFL = cast[proc(x: cint) {.noconv,gcsafe.}](0) 455 | SIG_IGN = cast[proc(x: cint) {.noconv,gcsafe.}](1) 456 | else: 457 | const 458 | ERROR_NO_DATA = 232 459 | 460 | var outBuffer = "TEST STRING BUFFER" 461 | 462 | block test1: 463 | # simple read/write test 464 | var inBuffer = newString(64) 465 | var o = createPipe() 466 | var sc = waitFor write(o, cast[pointer](addr outBuffer[0]), 467 | outBuffer.len) 468 | doAssert(sc == len(outBuffer)) 469 | var rc = waitFor readInto(o, cast[pointer](addr inBuffer[0]), 470 | inBuffer.len) 471 | inBuffer.setLen(rc) 472 | doAssert(inBuffer == outBuffer) 473 | close(o) 474 | 475 | block test2: 476 | # read from pipe closed write side 477 | var inBuffer = newString(64) 478 | var o = createPipe() 479 | o.closeWrite() 480 | var rc = waitFor readInto(o, cast[pointer](addr inBuffer[0]), 481 | inBuffer.len) 482 | doAssert(rc == 0) 483 | 484 | block test3: 485 | # write to closed read side 486 | var sc: int = -1 487 | var o = createPipe() 488 | o.closeRead() 489 | when not defined(windows): 490 | posix.signal(SIGPIPE, SIG_IGN) 491 | 492 | try: 493 | sc = waitFor write(o, cast[pointer](addr outBuffer[0]), 494 | outBuffer.len) 495 | except: 496 | discard 497 | doAssert(sc == -1) 498 | 499 | when not defined(windows): 500 | doAssert(osLastError().int32 == EPIPE) 501 | else: 502 | doAssert(osLastError().int32 == ERROR_NO_DATA) 503 | 504 | when not defined(windows): 505 | posix.signal(SIGPIPE, SIG_DFL) 506 | 507 | block test4: 508 | # bulk test of sending/receiving data 509 | const 510 | testsCount = 5000 511 | 512 | proc sender(o: AsyncPipe) {.async.} = 513 | var data = 1'i32 514 | for i in 1..testsCount: 515 | data = int32(i) 516 | let res = await write(o, addr data, sizeof(int32)) 517 | doAssert(res == sizeof(int32)) 518 | closeWrite(o) 519 | 520 | proc receiver(o: AsyncPipe): Future[tuple[count: int, sum: int]] {.async.} = 521 | var data = 0'i32 522 | result = (count: 0, sum: 0) 523 | while true: 524 | let res = await readInto(o, addr data, sizeof(int32)) 525 | if res == 0: 526 | break 527 | doAssert(res == sizeof(int32)) 528 | inc(result.sum, data) 529 | inc(result.count) 530 | 531 | var o = createPipe() 532 | asyncCheck sender(o) 533 | let res = waitFor(receiver(o)) 534 | doAssert(res.count == testsCount) 535 | doAssert(res.sum == testsCount * (1 + testsCount) div 2) 536 | 537 | -------------------------------------------------------------------------------- /asynctools/asyncproc.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Asynchronous tools for Nim Language 4 | # (c) Copyright 2016 Eugene Kabanov 5 | # 6 | # See the file "LICENSE", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## This module implements an advanced facility for executing OS processes 11 | ## and process communication in asynchronous way. 12 | ## 13 | ## Most code for this module is borrowed from original ``osproc.nim`` by 14 | ## Andreas Rumpf, with some extensions, improvements and fixes. 15 | ## 16 | ## API is near compatible with stdlib's ``osproc.nim``. 17 | 18 | import strutils, os, strtabs 19 | import asyncdispatch, asyncpipe 20 | 21 | when defined(windows): 22 | import winlean 23 | else: 24 | const STILL_ACTIVE = 259 25 | import posix 26 | 27 | when defined(linux): 28 | import linux 29 | 30 | type 31 | ProcessOption* = enum ## options that can be passed `startProcess` 32 | poEchoCmd, ## echo the command before execution 33 | poUsePath, ## Asks system to search for executable using PATH 34 | ## environment variable. 35 | ## On Windows, this is the default. 36 | poEvalCommand, ## Pass `command` directly to the shell, without 37 | ## quoting. 38 | ## Use it only if `command` comes from trusted source. 39 | poStdErrToStdOut, ## merge stdout and stderr to the stdout stream 40 | poParentStreams, ## use the parent's streams 41 | poInteractive, ## optimize the buffer handling for responsiveness for 42 | ## UI applications. Currently this only affects 43 | ## Windows: Named pipes are used so that you can peek 44 | ## at the process' output streams. 45 | poDemon ## Windows: The program creates no Window. 46 | 47 | AsyncProcessObj = object of RootObj 48 | inPipe: AsyncPipe 49 | outPipe: AsyncPipe 50 | errPipe: AsyncPipe 51 | 52 | when defined(windows): 53 | fProcessHandle: Handle 54 | fThreadHandle: Handle 55 | procId: int32 56 | threadId: int32 57 | isWow64: bool 58 | else: 59 | procId: Pid 60 | isExit: bool 61 | exitCode: cint 62 | options: set[ProcessOption] 63 | 64 | AsyncProcess* = ref AsyncProcessObj ## represents an operating system process 65 | 66 | proc quoteShellWindows*(s: string): string = 67 | ## Quote s, so it can be safely passed to Windows API. 68 | ## 69 | ## Based on Python's subprocess.list2cmdline 70 | ## 71 | ## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx 72 | let needQuote = {' ', '\t'} in s or s.len == 0 73 | 74 | result = "" 75 | var backslashBuff = "" 76 | if needQuote: 77 | result.add("\"") 78 | 79 | for c in s: 80 | if c == '\\': 81 | backslashBuff.add(c) 82 | elif c == '\"': 83 | result.add(backslashBuff) 84 | result.add(backslashBuff) 85 | backslashBuff.setLen(0) 86 | result.add("\\\"") 87 | else: 88 | if backslashBuff.len != 0: 89 | result.add(backslashBuff) 90 | backslashBuff.setLen(0) 91 | result.add(c) 92 | 93 | if needQuote: 94 | result.add("\"") 95 | 96 | proc quoteShellPosix*(s: string): string = 97 | ## Quote ``s``, so it can be safely passed to POSIX shell. 98 | ## 99 | ## Based on Python's pipes.quote 100 | const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@', 101 | '0'..'9', 'A'..'Z', 'a'..'z'} 102 | if s.len == 0: 103 | return "''" 104 | 105 | let safe = s.allCharsInSet(safeUnixChars) 106 | 107 | if safe: 108 | return s 109 | else: 110 | return "'" & s.replace("'", "'\"'\"'") & "'" 111 | 112 | proc quoteShell*(s: string): string = 113 | ## Quote ``s``, so it can be safely passed to shell. 114 | when defined(Windows): 115 | return quoteShellWindows(s) 116 | elif defined(posix): 117 | return quoteShellPosix(s) 118 | else: 119 | {.error:"quoteShell is not supported on your system".} 120 | 121 | 122 | proc execProcess*(command: string, args: seq[string] = @[], 123 | env: StringTableRef = nil, 124 | options: set[ProcessOption] = {poStdErrToStdOut, poUsePath, 125 | poEvalCommand} 126 | ): Future[tuple[exitcode: int, output: string]] {.async.} 127 | ## A convenience asynchronous procedure that executes ``command`` 128 | ## with ``startProcess`` and returns its exit code and output as a tuple. 129 | ## 130 | ## **WARNING**: this function uses poEvalCommand by default for backward 131 | ## compatibility. Make sure to pass options explicitly. 132 | ## 133 | ## .. code-block:: Nim 134 | ## 135 | ## let outp = await execProcess("nim c -r mytestfile.nim") 136 | ## echo "process exited with code = " & $outp.exitcode 137 | ## echo "process output = " & outp.output 138 | 139 | proc startProcess*(command: string, workingDir: string = "", 140 | args: openArray[string] = [], 141 | env: StringTableRef = nil, 142 | options: set[ProcessOption] = {poStdErrToStdOut}, 143 | pipeStdin: AsyncPipe = nil, 144 | pipeStdout: AsyncPipe = nil, 145 | pipeStderr: AsyncPipe = nil): AsyncProcess 146 | ## Starts a process. 147 | ## 148 | ## ``command`` is the executable file path 149 | ## 150 | ## ``workingDir`` is the process's working directory. If ``workingDir == ""`` 151 | ## the current directory is used. 152 | ## 153 | ## ``args`` are the command line arguments that are passed to the 154 | ## process. On many operating systems, the first command line argument is the 155 | ## name of the executable. ``args`` should not contain this argument! 156 | ## 157 | ## ``env`` is the environment that will be passed to the process. 158 | ## If ``env == nil`` the environment is inherited of 159 | ## the parent process. 160 | ## 161 | ## ``options`` are additional flags that may be passed 162 | ## to `startProcess`. See the documentation of ``ProcessOption`` for the 163 | ## meaning of these flags. 164 | ## 165 | ## ``pipeStdin``, ``pipeStdout``, ``pipeStderr`` is ``AsyncPipe`` handles 166 | ## which will be used as ``STDIN``, ``STDOUT`` and ``STDERR`` of started 167 | ## process respectively. This handles are optional, unspecified handles 168 | ## will be created automatically. 169 | ## 170 | ## Note that you can't pass any ``args`` if you use the option 171 | ## ``poEvalCommand``, which invokes the system shell to run the specified 172 | ## ``command``. In this situation you have to concatenate manually the 173 | ## contents of ``args`` to ``command`` carefully escaping/quoting any special 174 | ## characters, since it will be passed *as is* to the system shell. 175 | ## Each system/shell may feature different escaping rules, so try to avoid 176 | ## this kind of shell invocation if possible as it leads to non portable 177 | ## software. 178 | ## 179 | ## Return value: The newly created process object. Nil is never returned, 180 | ## but ``EOS`` is raised in case of an error. 181 | 182 | proc suspend*(p: AsyncProcess) 183 | ## Suspends the process ``p``. 184 | ## 185 | ## On Posix OSes the procedure sends ``SIGSTOP`` signal to the process. 186 | ## 187 | ## On Windows procedure suspends main thread execution of process via 188 | ## ``SuspendThread()``. WOW64 processes is also supported. 189 | 190 | proc resume*(p: AsyncProcess) 191 | ## Resumes the process ``p``. 192 | ## 193 | ## On Posix OSes the procedure sends ``SIGCONT`` signal to the process. 194 | ## 195 | ## On Windows procedure resumes execution of main thread via 196 | ## ``ResumeThread()``. WOW64 processes is also supported. 197 | 198 | proc terminate*(p: AsyncProcess) 199 | ## Stop the process ``p``. On Posix OSes the procedure sends ``SIGTERM`` 200 | ## to the process. On Windows the Win32 API function ``TerminateProcess()`` 201 | ## is called to stop the process. 202 | 203 | proc kill*(p: AsyncProcess) 204 | ## Kill the process ``p``. On Posix OSes the procedure sends ``SIGKILL`` to 205 | ## the process. On Windows ``kill()`` is simply an alias for ``terminate()``. 206 | 207 | proc running*(p: AsyncProcess): bool 208 | ## Returns `true` if the process ``p`` is still running. Returns immediately. 209 | 210 | proc peekExitCode*(p: AsyncProcess): int 211 | ## Returns `STILL_ACTIVE` if the process is still running. 212 | ## Otherwise the process' exit code. 213 | 214 | proc processID*(p: AsyncProcess): int = 215 | ## Returns process ``p`` id. 216 | return p.procId 217 | 218 | proc inputHandle*(p: AsyncProcess): AsyncPipe {.inline.} = 219 | ## Returns ``AsyncPipe`` handle to ``STDIN`` pipe of process ``p``. 220 | result = p.inPipe 221 | 222 | proc outputHandle*(p: AsyncProcess): AsyncPipe {.inline.} = 223 | ## Returns ``AsyncPipe`` handle to ``STDOUT`` pipe of process ``p``. 224 | result = p.outPipe 225 | 226 | proc errorHandle*(p: AsyncProcess): AsyncPipe {.inline.} = 227 | ## Returns ``AsyncPipe`` handle to ``STDERR`` pipe of process ``p``. 228 | result = p.errPipe 229 | 230 | proc waitForExit*(p: AsyncProcess): Future[int] 231 | ## Waits for the process to finish in asynchronous way and returns 232 | ## exit code. 233 | 234 | when defined(windows): 235 | 236 | const 237 | STILL_ACTIVE = 0x00000103'i32 238 | HANDLE_FLAG_INHERIT = 0x00000001'i32 239 | 240 | proc isWow64Process(hProcess: Handle, wow64Process: var WinBool): WinBool 241 | {.importc: "IsWow64Process", stdcall, dynlib: "kernel32".} 242 | proc wow64SuspendThread(hThread: Handle): Dword 243 | {.importc: "Wow64SuspendThread", stdcall, dynlib: "kernel32".} 244 | proc setHandleInformation(hObject: Handle, dwMask: Dword, 245 | dwFlags: Dword): WinBool 246 | {.importc: "SetHandleInformation", stdcall, dynlib: "kernel32".} 247 | 248 | proc buildCommandLine(a: string, args: openArray[string]): cstring = 249 | var res = quoteShell(a) 250 | for i in 0..high(args): 251 | res.add(' ') 252 | res.add(quoteShell(args[i])) 253 | result = cast[cstring](alloc0(res.len+1)) 254 | copyMem(result, cstring(res), res.len) 255 | 256 | proc buildEnv(env: StringTableRef): tuple[str: cstring, len: int] = 257 | var L = 0 258 | for key, val in pairs(env): inc(L, key.len + val.len + 2) 259 | var str = cast[cstring](alloc0(L+2)) 260 | L = 0 261 | for key, val in pairs(env): 262 | var x = key & "=" & val 263 | copyMem(addr(str[L]), cstring(x), x.len+1) # copy \0 264 | inc(L, x.len+1) 265 | (str, L) 266 | 267 | proc close(p: AsyncProcess) = 268 | if p.inPipe != nil: close(p.inPipe) 269 | if p.outPipe != nil: close(p.outPipe) 270 | if p.errPipe != nil: close(p.errPipe) 271 | 272 | proc startProcess(command: string, workingDir: string = "", 273 | args: openArray[string] = [], 274 | env: StringTableRef = nil, 275 | options: set[ProcessOption] = {poStdErrToStdOut}, 276 | pipeStdin: AsyncPipe = nil, 277 | pipeStdout: AsyncPipe = nil, 278 | pipeStderr: AsyncPipe = nil): AsyncProcess = 279 | var 280 | si: STARTUPINFO 281 | procInfo: PROCESS_INFORMATION 282 | 283 | result = AsyncProcess(options: options, isExit: true) 284 | si.cb = sizeof(STARTUPINFO).cint 285 | 286 | if not isNil(pipeStdin): 287 | si.hStdInput = pipeStdin.getReadHandle() 288 | 289 | # Mark other side of pipe as non inheritable. 290 | let oh = pipeStdin.getWriteHandle() 291 | if oh != 0: 292 | if setHandleInformation(oh, HANDLE_FLAG_INHERIT, 0) == 0: 293 | raiseOSError(osLastError()) 294 | else: 295 | if poParentStreams in options: 296 | si.hStdInput = getStdHandle(STD_INPUT_HANDLE) 297 | else: 298 | let pipe = createPipe() 299 | if poInteractive in options: 300 | result.inPipe = pipe 301 | si.hStdInput = pipe.getReadHandle() 302 | else: 303 | result.inPipe = pipe 304 | si.hStdInput = pipe.getReadHandle() 305 | 306 | if setHandleInformation(pipe.getWriteHandle(), 307 | HANDLE_FLAG_INHERIT, 0) == 0: 308 | raiseOSError(osLastError()) 309 | 310 | if not isNil(pipeStdout): 311 | si.hStdOutput = pipeStdout.getWriteHandle() 312 | 313 | # Mark other side of pipe as non inheritable. 314 | let oh = pipeStdout.getReadHandle() 315 | if oh != 0: 316 | if setHandleInformation(oh, HANDLE_FLAG_INHERIT, 0) == 0: 317 | raiseOSError(osLastError()) 318 | else: 319 | if poParentStreams in options: 320 | si.hStdOutput = getStdHandle(STD_OUTPUT_HANDLE) 321 | else: 322 | let pipe = createPipe() 323 | if poInteractive in options: 324 | result.outPipe = pipe 325 | si.hStdOutput = pipe.getWriteHandle() 326 | else: 327 | result.outPipe = pipe 328 | si.hStdOutput = pipe.getWriteHandle() 329 | if setHandleInformation(pipe.getReadHandle(), 330 | HANDLE_FLAG_INHERIT, 0) == 0: 331 | raiseOSError(osLastError()) 332 | 333 | if not isNil(pipeStderr): 334 | si.hStdError = pipeStderr.getWriteHandle() 335 | 336 | # Mark other side of pipe as non inheritable. 337 | let oh = pipeStderr.getReadHandle() 338 | if oh != 0: 339 | if setHandleInformation(oh, HANDLE_FLAG_INHERIT, 0) == 0: 340 | raiseOSError(osLastError()) 341 | else: 342 | if poParentStreams in options: 343 | si.hStdError = getStdHandle(STD_ERROR_HANDLE) 344 | else: 345 | if poInteractive in options: 346 | let pipe = createPipe() 347 | result.errPipe = pipe 348 | si.hStdError = pipe.getWriteHandle() 349 | if setHandleInformation(pipe.getReadHandle(), 350 | HANDLE_FLAG_INHERIT, 0) == 0: 351 | raiseOSError(osLastError()) 352 | else: 353 | if poStdErrToStdOut in options: 354 | result.errPipe = result.outPipe 355 | si.hStdError = si.hStdOutput 356 | else: 357 | let pipe = createPipe() 358 | result.errPipe = pipe 359 | si.hStdError = pipe.getWriteHandle() 360 | if setHandleInformation(pipe.getReadHandle(), 361 | HANDLE_FLAG_INHERIT, 0) == 0: 362 | raiseOSError(osLastError()) 363 | 364 | if si.hStdInput != 0 or si.hStdOutput != 0 or si.hStdError != 0: 365 | si.dwFlags = STARTF_USESTDHANDLES 366 | 367 | # building command line 368 | var cmdl: cstring 369 | if poEvalCommand in options: 370 | cmdl = buildCommandLine("cmd.exe", ["/c", command]) 371 | assert args.len == 0 372 | else: 373 | cmdl = buildCommandLine(command, args) 374 | # building environment 375 | var e = (str: nil.cstring, len: -1) 376 | if env != nil: e = buildEnv(env) 377 | # building working directory 378 | var wd: cstring = nil 379 | if len(workingDir) > 0: wd = workingDir 380 | # processing echo command line 381 | if poEchoCmd in options: echo($cmdl) 382 | # building security attributes for process and main thread 383 | var psa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint, 384 | lpSecurityDescriptor: nil, bInheritHandle: 1) 385 | var tsa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint, 386 | lpSecurityDescriptor: nil, bInheritHandle: 1) 387 | 388 | var tmp = newWideCString(cmdl) 389 | var ee = 390 | if e.str.isNil: newWideCString(cstring(nil)) 391 | else: newWideCString(e.str, e.len) 392 | var wwd = newWideCString(wd) 393 | var flags = NORMAL_PRIORITY_CLASS or CREATE_UNICODE_ENVIRONMENT 394 | if poDemon in options: flags = flags or CREATE_NO_WINDOW 395 | let res = winlean.createProcessW(nil, tmp, addr psa, addr tsa, 1, flags, 396 | ee, wwd, si, procInfo) 397 | if e.str != nil: dealloc(e.str) 398 | if res == 0: 399 | close(result) 400 | raiseOsError(osLastError()) 401 | else: 402 | result.fProcessHandle = procInfo.hProcess 403 | result.procId = procInfo.dwProcessId 404 | result.fThreadHandle = procInfo.hThread 405 | result.threadId = procInfo.dwThreadId 406 | when sizeof(int) == 8: 407 | # If sizeof(int) == 8, then our process is 64bit, and we need to check 408 | # architecture of just spawned process. 409 | var iswow64 = WinBool(0) 410 | if isWow64Process(procInfo.hProcess, iswow64) == 0: 411 | raiseOsError(osLastError()) 412 | result.isWow64 = (iswow64 != 0) 413 | else: 414 | result.isWow64 = false 415 | 416 | result.isExit = false 417 | 418 | if poParentStreams notin options: 419 | closeRead(result.inPipe) 420 | closeWrite(result.outPipe) 421 | closeWrite(result.errPipe) 422 | 423 | proc suspend(p: AsyncProcess) = 424 | var res = 0'i32 425 | if p.isWow64: 426 | res = wow64SuspendThread(p.fThreadHandle) 427 | else: 428 | res = suspendThread(p.fThreadHandle) 429 | if res < 0: 430 | raiseOsError(osLastError()) 431 | 432 | proc resume(p: AsyncProcess) = 433 | let res = resumeThread(p.fThreadHandle) 434 | if res < 0: 435 | raiseOsError(osLastError()) 436 | 437 | proc running(p: AsyncProcess): bool = 438 | var value = 0'i32 439 | let res = getExitCodeProcess(p.fProcessHandle, value) 440 | if res == 0: 441 | raiseOsError(osLastError()) 442 | else: 443 | if value == STILL_ACTIVE: 444 | result = true 445 | else: 446 | p.isExit = true 447 | p.exitCode = value 448 | 449 | proc terminate(p: AsyncProcess) = 450 | if running(p): 451 | discard terminateProcess(p.fProcessHandle, 0) 452 | 453 | proc kill(p: AsyncProcess) = 454 | terminate(p) 455 | 456 | proc peekExitCode(p: AsyncProcess): int = 457 | if p.isExit: 458 | result = p.exitCode 459 | else: 460 | var value = 0'i32 461 | let res = getExitCodeProcess(p.fProcessHandle, value) 462 | if res == 0: 463 | raiseOsError(osLastError()) 464 | else: 465 | result = value 466 | if value != STILL_ACTIVE: 467 | p.isExit = true 468 | p.exitCode = value 469 | 470 | when declared(addProcess): 471 | proc waitForExit(p: AsyncProcess): Future[int] = 472 | var retFuture = newFuture[int]("asyncproc.waitForExit") 473 | 474 | proc cb(fd: AsyncFD): bool = 475 | var value = 0'i32 476 | let res = getExitCodeProcess(p.fProcessHandle, value) 477 | if res == 0: 478 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 479 | else: 480 | p.isExit = true 481 | p.exitCode = value 482 | retFuture.complete(p.exitCode) 483 | 484 | if p.isExit: 485 | retFuture.complete(p.exitCode) 486 | else: 487 | addProcess(p.procId, cb) 488 | return retFuture 489 | 490 | else: 491 | const 492 | readIdx = 0 493 | writeIdx = 1 494 | 495 | template statusToExitCode(status): int32 = 496 | (status and 0xFF00) shr 8 497 | 498 | proc envToCStringArray(t: StringTableRef): cstringArray = 499 | result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) 500 | var i = 0 501 | for key, val in pairs(t): 502 | var x = key & "=" & val 503 | result[i] = cast[cstring](alloc(x.len+1)) 504 | copyMem(result[i], addr(x[0]), x.len+1) 505 | inc(i) 506 | 507 | proc envToCStringArray(): cstringArray = 508 | var counter = 0 509 | for key, val in envPairs(): inc counter 510 | result = cast[cstringArray](alloc0((counter + 1) * sizeof(cstring))) 511 | var i = 0 512 | for key, val in envPairs(): 513 | var x = key.string & "=" & val.string 514 | result[i] = cast[cstring](alloc(x.len+1)) 515 | copyMem(result[i], addr(x[0]), x.len+1) 516 | inc(i) 517 | 518 | type StartProcessData = object 519 | sysCommand: cstring 520 | sysArgs: cstringArray 521 | sysEnv: cstringArray 522 | workingDir: cstring 523 | pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint] 524 | options: set[ProcessOption] 525 | 526 | const useProcessAuxSpawn = declared(posix_spawn) and not defined(useFork) and 527 | not defined(useClone) and not defined(linux) 528 | when useProcessAuxSpawn: 529 | proc startProcessAuxSpawn(data: StartProcessData): Pid {. 530 | tags: [ExecIOEffect, ReadEnvEffect], gcsafe.} 531 | else: 532 | proc startProcessAuxFork(data: StartProcessData): Pid {. 533 | tags: [ExecIOEffect, ReadEnvEffect], gcsafe.} 534 | 535 | {.push stacktrace: off, profiler: off.} 536 | proc startProcessAfterFork(data: ptr StartProcessData) {. 537 | tags: [ExecIOEffect, ReadEnvEffect], cdecl, gcsafe.} 538 | {.pop.} 539 | 540 | proc startProcess(command: string, workingDir: string = "", 541 | args: openArray[string] = [], 542 | env: StringTableRef = nil, 543 | options: set[ProcessOption] = {poStdErrToStdOut}, 544 | pipeStdin: AsyncPipe = nil, 545 | pipeStdout: AsyncPipe = nil, 546 | pipeStderr: AsyncPipe = nil): AsyncProcess = 547 | var sd = StartProcessData() 548 | 549 | result = AsyncProcess(options: options, isExit: true) 550 | 551 | if not isNil(pipeStdin): 552 | sd.pStdin = pipeStdin.getHandles() 553 | else: 554 | if poParentStreams notin options: 555 | let pipe = createPipe() 556 | sd.pStdin = pipe.getHandles() 557 | result.inPipe = pipe 558 | 559 | if not isNil(pipeStdout): 560 | sd.pStdout = pipeStdout.getHandles() 561 | else: 562 | if poParentStreams notin options: 563 | let pipe = createPipe() 564 | sd.pStdout = pipe.getHandles() 565 | result.outPipe = pipe 566 | 567 | if not isNil(pipeStderr): 568 | sd.pStderr = pipeStderr.getHandles() 569 | else: 570 | if poParentStreams notin options: 571 | if poStdErrToStdOut in options: 572 | sd.pStderr = sd.pStdout 573 | result.errPipe = result.outPipe 574 | else: 575 | let pipe = createPipe() 576 | sd.pStderr = pipe.getHandles() 577 | result.errPipe = pipe 578 | 579 | var sysCommand: string 580 | var sysArgsRaw: seq[string] 581 | 582 | if poEvalCommand in options: 583 | sysCommand = "/bin/sh" 584 | sysArgsRaw = @[sysCommand, "-c", command] 585 | assert args.len == 0, "`args` has to be empty when using poEvalCommand." 586 | else: 587 | sysCommand = command 588 | sysArgsRaw = @[command] 589 | for arg in args.items: 590 | sysArgsRaw.add arg 591 | 592 | var pid: Pid 593 | 594 | var sysArgs = allocCStringArray(sysArgsRaw) 595 | defer: deallocCStringArray(sysArgs) 596 | 597 | var sysEnv = if env == nil: 598 | envToCStringArray() 599 | else: 600 | envToCStringArray(env) 601 | defer: deallocCStringArray(sysEnv) 602 | 603 | sd.sysCommand = sysCommand 604 | sd.sysArgs = sysArgs 605 | sd.sysEnv = sysEnv 606 | sd.options = options 607 | sd.workingDir = workingDir 608 | 609 | when useProcessAuxSpawn: 610 | let currentDir = getCurrentDir() 611 | pid = startProcessAuxSpawn(sd) 612 | if workingDir.len > 0: 613 | setCurrentDir(currentDir) 614 | else: 615 | pid = startProcessAuxFork(sd) 616 | 617 | # Parent process. Copy process information. 618 | if poEchoCmd in options: 619 | echo(command, " ", join(args, " ")) 620 | result.procId = pid 621 | 622 | result.isExit = false 623 | 624 | if poParentStreams notin options: 625 | closeRead(result.inPipe) 626 | closeWrite(result.outPipe) 627 | closeWrite(result.errPipe) 628 | 629 | when useProcessAuxSpawn: 630 | proc startProcessAuxSpawn(data: StartProcessData): Pid = 631 | var attr: Tposix_spawnattr 632 | var fops: Tposix_spawn_file_actions 633 | 634 | template chck(e: untyped) = 635 | if e != 0'i32: raiseOSError(osLastError()) 636 | 637 | chck posix_spawn_file_actions_init(fops) 638 | chck posix_spawnattr_init(attr) 639 | 640 | var mask: Sigset 641 | chck sigemptyset(mask) 642 | chck posix_spawnattr_setsigmask(attr, mask) 643 | 644 | var flags = POSIX_SPAWN_USEVFORK or POSIX_SPAWN_SETSIGMASK 645 | if poDemon in data.options: 646 | flags = flags or POSIX_SPAWN_SETPGROUP 647 | chck posix_spawnattr_setpgroup(attr, 0'i32) 648 | 649 | chck posix_spawnattr_setflags(attr, flags) 650 | 651 | if not (poParentStreams in data.options): 652 | chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx]) 653 | chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], 654 | readIdx) 655 | chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) 656 | chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 657 | writeIdx) 658 | if (poStdErrToStdOut in data.options): 659 | chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) 660 | else: 661 | chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) 662 | chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) 663 | 664 | var res: cint 665 | if data.workingDir.len > 0: 666 | setCurrentDir($data.workingDir) 667 | var pid: Pid 668 | 669 | if (poUsePath in data.options): 670 | res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, 671 | data.sysEnv) 672 | else: 673 | res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, 674 | data.sysEnv) 675 | 676 | discard posix_spawn_file_actions_destroy(fops) 677 | discard posix_spawnattr_destroy(attr) 678 | chck res 679 | return pid 680 | else: 681 | proc startProcessAuxFork(data: StartProcessData): Pid = 682 | if pipe(data.pErrorPipe) != 0: 683 | raiseOSError(osLastError()) 684 | 685 | defer: 686 | discard close(data.pErrorPipe[readIdx]) 687 | 688 | var pid: Pid 689 | var dataCopy = data 690 | 691 | when defined(useClone): 692 | const stackSize = 65536 693 | let stackEnd = cast[clong](alloc(stackSize)) 694 | let stack = cast[pointer](stackEnd + stackSize) 695 | let fn: pointer = startProcessAfterFork 696 | pid = clone(fn, stack, 697 | cint(CLONE_VM or CLONE_VFORK or SIGCHLD), 698 | pointer(addr dataCopy), nil, nil, nil) 699 | discard close(data.pErrorPipe[writeIdx]) 700 | dealloc(stack) 701 | else: 702 | pid = fork() 703 | if pid == 0: 704 | startProcessAfterFork(addr(dataCopy)) 705 | exitnow(1) 706 | 707 | discard close(data.pErrorPipe[writeIdx]) 708 | if pid < 0: raiseOSError(osLastError()) 709 | 710 | var error: cint 711 | 712 | var res = read(data.pErrorPipe[readIdx], addr error, sizeof(error)) 713 | if res == sizeof(error): 714 | raiseOSError(osLastError(), 715 | "Could not find command: '$1'. OS error: $2" % 716 | [$data.sysCommand, $strerror(error)]) 717 | return pid 718 | 719 | {.push stacktrace: off, profiler: off.} 720 | proc startProcessFail(data: ptr StartProcessData) = 721 | var error: cint = errno 722 | discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) 723 | exitnow(1) 724 | 725 | when not defined(uClibc) and (not defined(linux) or defined(android)): 726 | var environ {.importc.}: cstringArray 727 | 728 | proc startProcessAfterFork(data: ptr StartProcessData) = 729 | # Warning: no GC here! 730 | # Or anything that touches global structures - all called nim procs 731 | # must be marked with stackTrace:off. Inspect C code after making changes. 732 | if (poDemon in data.options): 733 | if posix.setpgid(Pid(0), Pid(0)) != 0: 734 | startProcessFail(data) 735 | 736 | if not (poParentStreams in data.options): 737 | if posix.close(data.pStdin[writeIdx]) != 0: 738 | startProcessFail(data) 739 | 740 | if dup2(data.pStdin[readIdx], readIdx) < 0: 741 | startProcessFail(data) 742 | 743 | if posix.close(data.pStdout[readIdx]) != 0: 744 | startProcessFail(data) 745 | 746 | if dup2(data.pStdout[writeIdx], writeIdx) < 0: 747 | startProcessFail(data) 748 | 749 | if (poStdErrToStdOut in data.options): 750 | if dup2(data.pStdout[writeIdx], 2) < 0: 751 | startProcessFail(data) 752 | else: 753 | if posix.close(data.pStderr[readIdx]) != 0: 754 | startProcessFail(data) 755 | 756 | if dup2(data.pStderr[writeIdx], 2) < 0: 757 | startProcessFail(data) 758 | 759 | if data.workingDir.len > 0: 760 | if chdir(data.workingDir) < 0: 761 | startProcessFail(data) 762 | 763 | if posix.close(data.pErrorPipe[readIdx]) != 0: 764 | startProcessFail(data) 765 | 766 | discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) 767 | 768 | if (poUsePath in data.options): 769 | when defined(uClibc): 770 | # uClibc environment (OpenWrt included) doesn't have the full execvpe 771 | discard execve(data.sysCommand, data.sysArgs, data.sysEnv) 772 | elif defined(linux) and not defined(android): 773 | discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv) 774 | else: 775 | # MacOSX doesn't have execvpe, so we need workaround. 776 | # On MacOSX we can arrive here only from fork, so this is safe: 777 | environ = data.sysEnv 778 | discard execvp(data.sysCommand, data.sysArgs) 779 | else: 780 | discard execve(data.sysCommand, data.sysArgs, data.sysEnv) 781 | 782 | startProcessFail(data) 783 | {.pop} 784 | 785 | proc close(p: AsyncProcess) = 786 | ## We need to `wait` for process, to avoid `zombie`, so if `running()` 787 | ## returns `false`, then process exited and `wait()` was called. 788 | doAssert(not p.running()) 789 | if p.inPipe != nil: close(p.inPipe) 790 | if p.outPipe != nil: close(p.outPipe) 791 | if p.errPipe != nil: close(p.errPipe) 792 | 793 | proc running(p: AsyncProcess): bool = 794 | result = true 795 | if p.isExit: 796 | result = false 797 | else: 798 | var status = cint(0) 799 | let res = posix.waitpid(p.procId, status, WNOHANG) 800 | if res == 0: 801 | result = true 802 | elif res < 0: 803 | raiseOsError(osLastError()) 804 | else: 805 | if WIFEXITED(status) or WIFSIGNALED(status): 806 | p.isExit = true 807 | p.exitCode = statusToExitCode(status) 808 | result = false 809 | 810 | proc peekExitCode(p: AsyncProcess): int = 811 | if p.isExit: 812 | result = p.exitCode 813 | else: 814 | var status = cint(0) 815 | let res = posix.waitpid(p.procId, status, WNOHANG) 816 | if res < 0: 817 | raiseOsError(osLastError()) 818 | elif res > 0: 819 | p.isExit = true 820 | p.exitCode = statusToExitCode(status) 821 | result = p.exitCode 822 | else: 823 | result = STILL_ACTIVE 824 | 825 | proc suspend(p: AsyncProcess) = 826 | if posix.kill(p.procId, SIGSTOP) != 0'i32: 827 | raiseOsError(osLastError()) 828 | 829 | proc resume(p: AsyncProcess) = 830 | if posix.kill(p.procId, SIGCONT) != 0'i32: 831 | raiseOsError(osLastError()) 832 | 833 | proc terminate(p: AsyncProcess) = 834 | if posix.kill(p.procId, SIGTERM) != 0'i32: 835 | raiseOsError(osLastError()) 836 | 837 | proc kill(p: AsyncProcess) = 838 | if posix.kill(p.procId, SIGKILL) != 0'i32: 839 | raiseOsError(osLastError()) 840 | 841 | when declared(addProcess): 842 | proc waitForExit*(p: AsyncProcess): Future[int] = 843 | var retFuture = newFuture[int]("asyncproc.waitForExit") 844 | 845 | proc cb(fd: AsyncFD): bool = 846 | var status = cint(0) 847 | let res = posix.waitpid(p.procId, status, WNOHANG) 848 | if res <= 0: 849 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 850 | else: 851 | p.isExit = true 852 | p.exitCode = statusToExitCode(status) 853 | retFuture.complete(p.exitCode) 854 | 855 | if p.isExit: 856 | retFuture.complete(p.exitCode) 857 | else: 858 | while true: 859 | var status = cint(0) 860 | let res = posix.waitpid(p.procId, status, WNOHANG) 861 | if res < 0: 862 | retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) 863 | break 864 | elif res > 0: 865 | p.isExit = true 866 | p.exitCode = statusToExitCode(status) 867 | retFuture.complete(p.exitCode) 868 | break 869 | else: 870 | try: 871 | addProcess(p.procId, cb) 872 | break 873 | except: 874 | let err = osLastError() 875 | if cint(err) == ESRCH: 876 | continue 877 | else: 878 | retFuture.fail(newException(OSError, osErrorMsg(err))) 879 | break 880 | return retFuture 881 | 882 | proc execProcess(command: string, args: seq[string] = @[], 883 | env: StringTableRef = nil, 884 | options: set[ProcessOption] = {poStdErrToStdOut, poUsePath, 885 | poEvalCommand} 886 | ): Future[tuple[exitcode: int, output: string]] {.async.} = 887 | result = (exitcode: int(STILL_ACTIVE), output: "") 888 | let bufferSize = 1024 889 | var data = newString(bufferSize) 890 | var p = startProcess(command, args = args, env = env, options = options) 891 | 892 | while true: 893 | let res = await p.outputHandle.readInto(addr data[0], bufferSize) 894 | if res > 0: 895 | data.setLen(res) 896 | result.output &= data 897 | data.setLen(bufferSize) 898 | else: 899 | break 900 | result.exitcode = await p.waitForExit() 901 | close(p) 902 | 903 | when isMainModule: 904 | import os 905 | 906 | when defined(windows): 907 | var data = waitFor(execProcess("cd")) 908 | else: 909 | var data = waitFor(execProcess("pwd")) 910 | echo "exitCode = " & $data.exitcode 911 | echo "output = [" & $data.output & "]" 912 | -------------------------------------------------------------------------------- /asynctools/asyncpty.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Asynchronous tools for Nim Language 4 | # (c) Copyright 2016 Eugene Kabanov 5 | # 6 | # See the file "LICENSE", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## This module implements cross-platform asynchronous PTY interface. 11 | ## 12 | ## Please note, that Windows does not support UNIX98 style virtual character 13 | ## devices, so named pipe with duplex access is used as replacement. If you 14 | ## want to display information from pipe you need to implement terminal by 15 | ## yourself. 16 | ## 17 | ## .. code-block:: nim 18 | ## var inBuffer = newString(64) 19 | ## var outBuffer = "Hello world!" 20 | ## 21 | ## # Create new pipe 22 | ## var o = newAsyncPty() 23 | ## 24 | ## # show pty name 25 | ## echo "pty device name is " & o.name 26 | ## 27 | ## # Read data from pty 28 | ## var c = waitFor readInto(o, cast[pointer](addr inBuffer[0]), inBuffer.len) 29 | ## 30 | ## # Close pty 31 | ## close(o) 32 | 33 | import asyncdispatch, os 34 | 35 | when defined(nimdoc): 36 | type 37 | AsyncPty* = ref object ## Object represents ``AsyncPty``. 38 | 39 | proc newAsyncPty*(): AsyncPty 40 | ## Creates new pseudoterminal device on Posix OSes or new Named Pipe on 41 | ## Windows with unique name. 42 | ## 43 | ## Returns ``AsyncPty`` object. 44 | 45 | proc close*(pty: AsyncPty) 46 | ## Closes pseudoterminal device. 47 | 48 | proc readInto*(pty: AsyncPty, data: pointer, nbytes: int): Future[int] 49 | ## This procedure reads up to ``size`` bytes from pty device ``pty`` 50 | ## into ``data``, which must at least be of that size. 51 | ## 52 | ## Returned future will complete once all the data requested is read or 53 | ## part of the data has been read. 54 | 55 | proc write*(pty: AsyncPty, data: pointer, nbytes: int): Future[int] 56 | ## This procedure writes an untyped ``data`` of ``size`` size to the 57 | ## pty device ``pty``. 58 | ## 59 | ## The returned future will complete once ``all`` data has been sent or 60 | ## part of the data has been sent. 61 | 62 | proc `$`*(pty: AsyncPty): string 63 | ## Returns string representation of ``AsyncPty`` object. 64 | 65 | else: 66 | 67 | when defined(windows): 68 | import winlean 69 | else: 70 | import posix 71 | 72 | when defined(windows): 73 | proc QueryPerformanceCounter(res: var int64) 74 | {.importc: "QueryPerformanceCounter", stdcall, dynlib: "kernel32".} 75 | proc connectNamedPipe(hNamedPipe: Handle, lpOverlapped: pointer): WINBOOL 76 | {.importc: "ConnectNamedPipe", stdcall, dynlib: "kernel32".} 77 | proc disconnectNamedPipe(hNamedPipe: Handle): WINBOOL 78 | {.importc: "DisconnectNamedPipe", stdcall, dynlib: "kernel32".} 79 | proc cancelIo(hFile: Handle): WINBOOL 80 | {.importc: "CancelIo", stdcall, dynlib: "kernel32".} 81 | 82 | const 83 | ptyHeaderName = r"\\.\pipe\asyncpty_" 84 | DEFAULT_PIPE_SIZE = 65536'i32 85 | FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000'i32 86 | PIPE_WAIT = 0x00000000'i32 87 | PIPE_TYPE_BYTE = 0x00000000'i32 88 | PIPE_READMODE_BYTE = 0x00000000'i32 89 | ERROR_PIPE_CONNECTED = 535 90 | ERROR_PIPE_BUSY = 231 91 | ERROR_BROKEN_PIPE = 109 92 | ERROR_PIPE_NOT_CONNECTED = 233 93 | ERROR_NO_DATA = 232 94 | PIPE_UNLIMITED_INSTANCES = 255'i32 95 | 96 | type 97 | CustomOverlapped = object of OVERLAPPED 98 | data*: CompletionData 99 | 100 | PCustomOverlapped* = ref CustomOverlapped 101 | 102 | AsyncPty* = ref object of RootRef 103 | name*: string 104 | fd: Handle 105 | confuture: Future[void] 106 | 107 | proc `$`*(pty: AsyncPty): string = 108 | result = "AsyncPty [name = \"" & pty.name & "\"" & 109 | ", handle = " & $(cast[int](pty.fd)) & "]" 110 | 111 | proc startConnect(pty: AsyncPty, reconnect = false) = 112 | if reconnect: 113 | if disconnectNamedPipe(pty.fd) == 0: 114 | raiseOSError(osLastError()) 115 | 116 | pty.confuture = newFuture[void]("asyncpty.startConnect") 117 | var ol = PCustomOverlapped() 118 | GC_ref(ol) 119 | ol.data = CompletionData(fd: AsyncFD(pty.fd), cb: 120 | proc(fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) = 121 | doAssert(pty.confuture != nil) 122 | if not pty.confuture.finished: 123 | if errcode == OSErrorCode(-1): 124 | pty.confuture.complete() 125 | else: 126 | pty.confuture.fail(newException(OSError, osErrorMsg(errcode))) 127 | ) 128 | 129 | let res = connectNamedPipe(pty.fd, cast[pointer](ol)) 130 | if res == 0: 131 | let err = osLastError() 132 | if err.int32 == ERROR_PIPE_CONNECTED: 133 | GC_unref(ol) 134 | pty.confuture.complete() 135 | elif err.int32 != ERROR_IO_PENDING: 136 | GC_unref(ol) 137 | pty.confuture.fail(newException(OSError, osErrorMsg(err))) 138 | raiseOSError(err) 139 | 140 | proc newAsyncPty*(): AsyncPty = 141 | var number = 0'i64 142 | var ptyWName: WideCString 143 | var ptyName: string 144 | var ptyHandle: Handle 145 | 146 | var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint, 147 | lpSecurityDescriptor: nil, bInheritHandle: 1) 148 | while true: 149 | QueryPerformanceCounter(number) 150 | ptyName = ptyHeaderName & $number 151 | ptyWName = newWideCString(ptyName) 152 | var openMode = FILE_FLAG_FIRST_PIPE_INSTANCE or FILE_FLAG_OVERLAPPED or 153 | PIPE_ACCESS_DUPLEX 154 | var pipeMode = PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT 155 | ptyHandle = createNamedPipe(ptyWName, openMode, pipeMode, 1'i32, 156 | DEFAULT_PIPE_SIZE, DEFAULT_PIPE_SIZE, 157 | PIPE_UNLIMITED_INSTANCES, addr sa) 158 | if ptyHandle == INVALID_HANDLE_VALUE: 159 | let err = osLastError() 160 | if err.int32 != ERROR_PIPE_BUSY: 161 | raiseOsError(err) 162 | else: 163 | break 164 | 165 | result = AsyncPty(fd: ptyHandle, name: ptyName) 166 | register(AsyncFD(ptyHandle)) 167 | 168 | startConnect(result, false) 169 | 170 | proc readIntoImpl(pty: AsyncPty, data: pointer, nbytes: int): Future[int] = 171 | var retFuture = newFuture[int]("asyncpty.readIntoImpl") 172 | var ol = PCustomOverlapped() 173 | 174 | GC_ref(ol) 175 | ol.data = CompletionData(fd: AsyncFD(pty.fd), cb: 176 | proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) = 177 | if not retFuture.finished: 178 | if errcode == OSErrorCode(-1): 179 | assert(bytesCount > 0 and bytesCount <= nbytes.int32) 180 | retFuture.complete(bytesCount) 181 | else: 182 | if errcode.int32 in {ERROR_BROKEN_PIPE, 183 | ERROR_PIPE_NOT_CONNECTED}: 184 | retFuture.complete(bytesCount) 185 | else: 186 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 187 | ) 188 | let res = readFile(pty.fd, data, nbytes.int32, nil, 189 | cast[POVERLAPPED](ol)).bool 190 | if not res: 191 | let err = osLastError() 192 | if err.int32 in {ERROR_BROKEN_PIPE, ERROR_PIPE_NOT_CONNECTED}: 193 | GC_unref(ol) 194 | retFuture.complete(0) 195 | elif err.int32 != ERROR_IO_PENDING: 196 | GC_unref(ol) 197 | retFuture.fail(newException(OSError, osErrorMsg(err))) 198 | return retFuture 199 | 200 | proc readInto*(pty: AsyncPty, data: pointer, 201 | nbytes: int): Future[int] {.async.} = 202 | if not pty.confuture.finished: 203 | await pty.confuture 204 | 205 | result = await readIntoImpl(pty, data, nbytes) 206 | 207 | if result == 0: 208 | startConnect(pty, true) 209 | 210 | proc writeImpl(pty: AsyncPty, data: pointer, nbytes: int): Future[int] = 211 | var retFuture = newFuture[int]("asyncpty.writeImpl") 212 | var ol = PCustomOverlapped() 213 | 214 | GC_ref(ol) 215 | ol.data = CompletionData(fd: AsyncFD(pty.fd), cb: 216 | proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) = 217 | if not retFuture.finished: 218 | if errcode == OSErrorCode(-1): 219 | retFuture.complete(bytesCount) 220 | else: 221 | if errcode.int32 == ERROR_NO_DATA: 222 | retFuture.complete(0) 223 | else: 224 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 225 | ) 226 | let res = writeFile(pty.fd, data, nbytes.int32, nil, 227 | cast[POVERLAPPED](ol)).bool 228 | if not res: 229 | let errcode = osLastError() 230 | if errcode.int32 == ERROR_NO_DATA: 231 | retFuture.complete(0) 232 | elif errcode.int32 != ERROR_IO_PENDING: 233 | GC_unref(ol) 234 | retFuture.fail(newException(OSError, osErrorMsg(errcode))) 235 | return retFuture 236 | 237 | proc write*(pty: AsyncPty, data: pointer, 238 | nbytes: int): Future[int] {.async.} = 239 | if not pty.confuture.finished: 240 | await pty.confuture 241 | 242 | result = await writeImpl(pty, data, nbytes) 243 | 244 | if result == 0: 245 | startConnect(pty, true) 246 | 247 | proc close*(pty: AsyncPty) = 248 | if not pty.confuture.finished: 249 | if cancelIo(pty.fd) == 0: 250 | raiseOSError(osLastError()) 251 | 252 | unregister(AsyncFD(pty.fd)) 253 | 254 | if closeHandle(pty.fd) == 0: 255 | raiseOSError(osLastError()) 256 | 257 | else: 258 | import posix 259 | 260 | proc posix_openpt(flags: cint): cint 261 | {.importc: "posix_openpt", header: """#include 262 | #include """.} 263 | proc grantpt(fildes: cint): cint 264 | {.importc: "grantpt", header: "".} 265 | proc unlockpt(fildes: cint): cint 266 | {.importc: "unlockpt", header: "".} 267 | proc ptsname(fildes: cint): cstring 268 | {.importc: "ptsname", header: "".} 269 | 270 | type 271 | AsyncPty* = ref object of RootRef 272 | name*: string 273 | fd: cint 274 | 275 | proc `$`*(pty: AsyncPty): string = 276 | result = "AsyncPty [name = \"" & pty.name & "\"" & 277 | ", handle = " & $(cast[int](pty.fd)) & "]" 278 | 279 | proc setNonBlocking(fd: cint) {.inline.} = 280 | var x = fcntl(fd, F_GETFL, 0) 281 | if x == -1: 282 | raiseOSError(osLastError()) 283 | else: 284 | var mode = x or O_NONBLOCK 285 | if fcntl(fd, F_SETFL, mode) == -1: 286 | raiseOSError(osLastError()) 287 | 288 | proc newAsyncPty*(): AsyncPty = 289 | let mfd = posix_openpt(posix.O_RDWR or posix.O_NOCTTY) 290 | if mfd == -1: 291 | raiseOSError(osLastError()) 292 | if grantpt(mfd) != 0: 293 | raiseOSError(osLastError()) 294 | if unlockpt(mfd) != 0: 295 | raiseOSError(osLastError()) 296 | 297 | setNonBlocking(mfd) 298 | result = AsyncPty(name: $ptsname(mfd), fd: mfd) 299 | register(AsyncFD(mfd)) 300 | 301 | proc write*(pty: AsyncPty, data: pointer, nbytes: int): Future[int] = 302 | var retFuture = newFuture[int]("asyncpty.write") 303 | var bytesWrote = 0 304 | 305 | proc cb(fd: AsyncFD): bool = 306 | result = true 307 | let reminder = nbytes - bytesWrote 308 | let pdata = cast[pointer](cast[uint](data) + bytesWrote.uint) 309 | let res = posix.write(pty.fd, pdata, cint(reminder)) 310 | if res < 0: 311 | let err = osLastError() 312 | if err.int32 != EAGAIN: 313 | retFuture.fail(newException(OSError, osErrorMsg(err))) 314 | else: 315 | result = false # We still want this callback to be called. 316 | elif res == 0: 317 | retFuture.complete(bytesWrote) 318 | else: 319 | bytesWrote.inc(res) 320 | if res != reminder: 321 | result = false 322 | else: 323 | retFuture.complete(bytesWrote) 324 | 325 | if not cb(AsyncFD(pty.fd)): 326 | addWrite(AsyncFD(pty.fd), cb) 327 | return retFuture 328 | 329 | proc readInto*(pty: AsyncPty, data: pointer, nbytes: int): Future[int] = 330 | var retFuture = newFuture[int]("asyncpipe.readInto") 331 | 332 | proc cb(fd: AsyncFD): bool = 333 | result = true 334 | let res = posix.read(pty.fd, data, cint(nbytes)) 335 | if res < 0: 336 | let err = osLastError() 337 | if err.int32 != EAGAIN: 338 | retFuture.fail(newException(OSError, osErrorMsg(err))) 339 | else: 340 | result = false # We still want this callback to be called. 341 | elif res == 0: 342 | retFuture.complete(0) 343 | else: 344 | retFuture.complete(res) 345 | 346 | if not cb(AsyncFD(pty.fd)): 347 | addRead(AsyncFD(pty.fd), cb) 348 | return retFuture 349 | 350 | proc close*(pty: AsyncPty) = 351 | unregister(AsyncFD(pty.fd)) 352 | if posix.close(pty.fd) != 0: 353 | raiseOSError(osLastError()) 354 | 355 | when isMainModule: 356 | var data = "Hello World!" 357 | var incomingData = newString(128) 358 | var pty = newAsyncPty() 359 | 360 | when defined(windows): 361 | var pipeName = newWideCString(pty.name) 362 | var openMode = (FILE_READ_DATA or FILE_WRITE_DATA or SYNCHRONIZE) 363 | var ptyHandle = createFileW(pipeName, openMode, 0, nil, OPEN_EXISTING, 364 | 0, 0) 365 | if ptyHandle == INVALID_HANDLE_VALUE: 366 | raiseOsError(osLastError()) 367 | 368 | if writeFile(ptyHandle, addr data[0], len(data).int32, nil, nil) == 0: 369 | raiseOSError(osLastError()) 370 | else: 371 | var fd = posix.open(pty.name, posix.O_RDWR) 372 | if fd == -1: 373 | raiseOSError(osLastError()) 374 | 375 | if posix.write(fd, addr data[0], len(data)) == -1: 376 | raiseOSError(osLastError()) 377 | 378 | let rc = waitFor(readInto(pty, addr incomingData[0], len(incomingData))) 379 | 380 | incomingData.setLen(rc) 381 | doAssert(data == incomingData) 382 | 383 | close(pty) 384 | -------------------------------------------------------------------------------- /asynctools/asyncsync.nim: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # AsyncSync 4 | # (c) Copyright 2018 Eugene Kabanov 5 | # 6 | # See the file "LICENSE", included in this 7 | # distribution, for details about the copyright. 8 | # 9 | 10 | ## This module implements some core synchronization primitives, which 11 | ## `asyncdispatch` is really lacking. 12 | import asyncdispatch, deques 13 | 14 | type 15 | AsyncLock* = ref object of RootRef 16 | ## A primitive lock is a synchronization primitive that is not owned by 17 | ## a particular coroutine when locked. A primitive lock is in one of two 18 | ## states, ``locked`` or ``unlocked``. 19 | ## 20 | ## When more than one coroutine is blocked in ``acquire()`` waiting for 21 | ## the state to turn to unlocked, only one coroutine proceeds when a 22 | ## ``release()`` call resets the state to unlocked; first coroutine which 23 | ## is blocked in ``acquire()`` is being processed. 24 | locked: bool 25 | waiters: Deque[Future[void]] 26 | 27 | AsyncEv* = ref object of RootRef 28 | ## A primitive event object. 29 | ## 30 | ## An event manages a flag that can be set to `true` with the ``fire()`` 31 | ## procedure and reset to `false` with the ``clear()`` procedure. 32 | ## The ``wait()`` coroutine blocks until the flag is `false`. 33 | ## 34 | ## If more than one coroutine blocked in ``wait()`` waiting for event 35 | ## state to be signaled, when event get fired, then all coroutines 36 | ## continue proceeds in order, they have entered waiting state. 37 | 38 | flag: bool 39 | waiters: Deque[Future[void]] 40 | 41 | AsyncQueue*[T] = ref object of RootRef 42 | ## A queue, useful for coordinating producer and consumer coroutines. 43 | ## 44 | ## If ``maxsize`` is less than or equal to zero, the queue size is 45 | ## infinite. If it is an integer greater than ``0``, then "await put()" 46 | ## will block when the queue reaches ``maxsize``, until an item is 47 | ## removed by "await get()". 48 | getters: Deque[Future[void]] 49 | putters: Deque[Future[void]] 50 | queue: Deque[T] 51 | maxsize: int 52 | 53 | AsyncQueueEmptyError* = object of CatchableError 54 | ## ``AsyncQueue`` is empty. 55 | AsyncQueueFullError* = object of CatchableError 56 | ## ``AsyncQueue`` is full. 57 | AsyncLockError* = object of CatchableError 58 | ## ``AsyncLock`` is either locked or unlocked. 59 | 60 | proc newAsyncLock*(): AsyncLock = 61 | ## Creates new asynchronous lock ``AsyncLock``. 62 | ## 63 | ## Lock is created in the unlocked state. When the state is unlocked, 64 | ## ``acquire()`` changes the state to locked and returns immediately. 65 | ## When the state is locked, ``acquire()`` blocks until a call to 66 | ## ``release()`` in another coroutine changes it to unlocked. 67 | ## 68 | ## The ``release()`` procedure changes the state to unlocked and returns 69 | ## immediately. 70 | 71 | # Workaround for callSoon() not worked correctly before 72 | # getGlobalDispatcher() call. 73 | discard getGlobalDispatcher() 74 | result = new AsyncLock 75 | result.waiters = initDeque[Future[void]]() 76 | result.locked = false 77 | 78 | proc acquire*(lock: AsyncLock) {.async.} = 79 | ## Acquire a lock ``lock``. 80 | ## 81 | ## This procedure blocks until the lock ``lock`` is unlocked, then sets it 82 | ## to locked and returns. 83 | if not lock.locked: 84 | lock.locked = true 85 | else: 86 | var w = newFuture[void]("asynclock.acquire") 87 | lock.waiters.addLast(w) 88 | yield w 89 | lock.locked = true 90 | 91 | proc own*(lock: AsyncLock) = 92 | ## Acquire a lock ``lock``. 93 | ## 94 | ## This procedure not blocks, if ``lock`` is locked, then ``AsyncLockError`` 95 | ## exception would be raised. 96 | if lock.locked: 97 | raise newException(AsyncLockError, "AsyncLock is already acquired!") 98 | lock.locked = true 99 | 100 | proc locked*(lock: AsyncLock): bool = 101 | ## Return `true` if the lock ``lock`` is acquired, `false` otherwise. 102 | result = lock.locked 103 | 104 | proc release*(lock: AsyncLock) = 105 | ## Release a lock ``lock``. 106 | ## 107 | ## When the ``lock`` is locked, reset it to unlocked, and return. If any 108 | ## other coroutines are blocked waiting for the lock to become unlocked, 109 | ## allow exactly one of them to proceed. 110 | var w: Future[void] 111 | proc wakeup() = w.complete() 112 | 113 | if lock.locked: 114 | lock.locked = false 115 | while len(lock.waiters) > 0: 116 | w = lock.waiters.popFirst() 117 | if not w.finished: 118 | callSoon(wakeup) 119 | break 120 | else: 121 | raise newException(AsyncLockError, "AsyncLock is not acquired!") 122 | 123 | proc newAsyncEv*(): AsyncEv = 124 | ## Creates new asyncronous event ``AsyncEv``. 125 | ## 126 | ## An event manages a flag that can be set to `true` with the `fire()` 127 | ## procedure and reset to `false` with the `clear()` procedure. 128 | ## The `wait()` procedure blocks until the flag is `true`. The flag is 129 | ## initially `false`. 130 | 131 | # Workaround for callSoon() not worked correctly before 132 | # getGlobalDispatcher() call. 133 | discard getGlobalDispatcher() 134 | result = new AsyncEv 135 | result.waiters = initDeque[Future[void]]() 136 | result.flag = false 137 | 138 | proc wait*(event: AsyncEv) {.async.} = 139 | ## Block until the internal flag of ``event`` is `true`. 140 | ## If the internal flag is `true` on entry, return immediately. Otherwise, 141 | ## block until another task calls `fire()` to set the flag to `true`, 142 | ## then return. 143 | if event.flag: 144 | discard 145 | else: 146 | var w = newFuture[void]("asyncev.wait") 147 | event.waiters.addLast(w) 148 | yield w 149 | 150 | proc fire*(event: AsyncEv) = 151 | ## Set the internal flag of ``event`` to `true`. All tasks waiting for it 152 | ## to become `true` are awakened. Task that call `wait()` once the flag is 153 | ## `true` will not block at all. 154 | proc wakeupAll() {.gcsafe.} = 155 | if len(event.waiters) > 0: 156 | var w = event.waiters.popFirst() 157 | if not w.finished: 158 | w.complete() 159 | callSoon(wakeupAll) 160 | 161 | if not event.flag: 162 | event.flag = true 163 | callSoon(wakeupAll) 164 | 165 | proc clear*(event: AsyncEv) = 166 | ## Reset the internal flag of ``event`` to `false`. Subsequently, tasks 167 | ## calling `wait()` will block until `fire()` is called to set the internal 168 | ## flag to `true` again. 169 | event.flag = false 170 | 171 | proc isSet*(event: AsyncEv): bool = 172 | ## Return `true` if and only if the internal flag of ``event`` is `true`. 173 | result = event.flag 174 | 175 | proc newAsyncQueue*[T](maxsize: int = 0): AsyncQueue[T] = 176 | ## Creates a new asynchronous queue ``AsyncQueue``. 177 | 178 | # Workaround for callSoon() not worked correctly before 179 | # getGlobalDispatcher() call. 180 | discard getGlobalDispatcher() 181 | result = new AsyncQueue[T] 182 | result.getters = initDeque[Future[void]]() 183 | result.putters = initDeque[Future[void]]() 184 | result.queue = initDeque[T]() 185 | result.maxsize = maxsize 186 | 187 | proc full*[T](aq: AsyncQueue[T]): bool {.inline.} = 188 | ## Return ``true`` if there are ``maxsize`` items in the queue. 189 | ## 190 | ## Note: If the ``aq`` was initialized with ``maxsize = 0`` (default), 191 | ## then ``full()`` is never ``true``. 192 | if aq.maxsize <= 0: 193 | result = false 194 | else: 195 | result = len(aq.queue) >= aq.maxsize 196 | 197 | proc empty*[T](aq: AsyncQueue[T]): bool {.inline.} = 198 | ## Return ``true`` if the queue is empty, ``false`` otherwise. 199 | result = (len(aq.queue) == 0) 200 | 201 | proc putNoWait*[T](aq: AsyncQueue[T], item: T) = 202 | ## Put an item into the queue ``aq`` immediately. 203 | ## 204 | ## If queue ``aq`` is full, then ``AsyncQueueFullError`` exception raised 205 | var w: Future[void] 206 | proc wakeup() = w.complete() 207 | 208 | if aq.full(): 209 | raise newException(AsyncQueueFullError, "AsyncQueue is full!") 210 | aq.queue.addLast(item) 211 | 212 | while len(aq.getters) > 0: 213 | w = aq.getters.popFirst() 214 | if not w.finished: 215 | callSoon(wakeup) 216 | 217 | proc getNoWait*[T](aq: AsyncQueue[T]): T = 218 | ## Remove and return ``item`` from the queue immediately. 219 | ## 220 | ## If queue ``aq`` is empty, then ``AsyncQueueEmptyError`` exception raised. 221 | var w: Future[void] 222 | proc wakeup() = w.complete() 223 | 224 | if aq.empty(): 225 | raise newException(AsyncQueueEmptyError, "AsyncQueue is empty!") 226 | result = aq.queue.popFirst() 227 | while len(aq.putters) > 0: 228 | w = aq.putters.popFirst() 229 | if not w.finished: 230 | callSoon(wakeup) 231 | 232 | proc put*[T](aq: AsyncQueue[T], item: T) {.async.} = 233 | ## Put an ``item`` into the queue ``aq``. If the queue is full, wait until 234 | ## a free slot is available before adding item. 235 | while aq.full(): 236 | var putter = newFuture[void]("asyncqueue.putter") 237 | aq.putters.addLast(putter) 238 | yield putter 239 | aq.putNoWait(item) 240 | 241 | proc get*[T](aq: AsyncQueue[T]): Future[T] {.async.} = 242 | ## Remove and return an item from the queue ``aq``. 243 | ## 244 | ## If queue is empty, wait until an item is available. 245 | while aq.empty(): 246 | var getter = newFuture[void]("asyncqueue.getter") 247 | aq.getters.addLast(getter) 248 | yield getter 249 | result = aq.getNoWait() 250 | 251 | proc len*[T](aq: AsyncQueue[T]): int {.inline.} = 252 | ## Return the number of elements in ``aq``. 253 | result = len(aq.queue) 254 | 255 | proc size*[T](aq: AsyncQueue[T]): int {.inline.} = 256 | ## Return the maximum number of elements in ``aq``. 257 | result = len(aq.maxsize) 258 | 259 | when isMainModule: 260 | # Locks test 261 | block: 262 | var test = "" 263 | var lock = newAsyncLock() 264 | 265 | proc testLock(n: int, lock: AsyncLock) {.async.} = 266 | await lock.acquire() 267 | test = test & $n 268 | lock.release() 269 | 270 | lock.own() 271 | asyncCheck testLock(0, lock) 272 | asyncCheck testLock(1, lock) 273 | asyncCheck testLock(2, lock) 274 | asyncCheck testLock(3, lock) 275 | asyncCheck testLock(4, lock) 276 | asyncCheck testLock(5, lock) 277 | asyncCheck testLock(6, lock) 278 | asyncCheck testLock(7, lock) 279 | asyncCheck testLock(8, lock) 280 | asyncCheck testLock(9, lock) 281 | lock.release() 282 | poll() 283 | doAssert(test == "0123456789") 284 | 285 | # Events test 286 | block: 287 | var test = "" 288 | var event = newAsyncEv() 289 | 290 | proc testEvent(n: int, ev: AsyncEv) {.async.} = 291 | await ev.wait() 292 | test = test & $n 293 | 294 | event.clear() 295 | asyncCheck testEvent(0, event) 296 | asyncCheck testEvent(1, event) 297 | asyncCheck testEvent(2, event) 298 | asyncCheck testEvent(3, event) 299 | asyncCheck testEvent(4, event) 300 | asyncCheck testEvent(5, event) 301 | asyncCheck testEvent(6, event) 302 | asyncCheck testEvent(7, event) 303 | asyncCheck testEvent(8, event) 304 | asyncCheck testEvent(9, event) 305 | event.fire() 306 | poll() 307 | doAssert(test == "0123456789") 308 | 309 | # Queues test 310 | block: 311 | const queueSize = 10 312 | const testsCount = 1000 313 | var test = 0 314 | 315 | proc task1(aq: AsyncQueue[int]) {.async.} = 316 | for i in 1..(testsCount - 1): 317 | var item = await aq.get() 318 | test -= item 319 | 320 | proc task2(aq: AsyncQueue[int]) {.async.} = 321 | for i in 1..testsCount: 322 | await aq.put(i) 323 | test += i 324 | 325 | var queue = newAsyncQueue[int](queueSize) 326 | discard task1(queue) or task2(queue) 327 | poll() 328 | doAssert(test == testsCount) 329 | -------------------------------------------------------------------------------- /doc/empty.txt: -------------------------------------------------------------------------------- 1 | This file keeps several tools from deleting this subdirectory. 2 | -------------------------------------------------------------------------------- /tests/bootstrap.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SETLOCAL enabledelayedexpansion 4 | 5 | IF "%NIM_DIR%" == "" ( 6 | ECHO NIM_DIR variable is not set 7 | EXIT /B 0 8 | ) 9 | 10 | IF "%NIM_BRANCH%" == "" ( 11 | ECHO NIM_BRANCH variable is not set 12 | EXIT /B 0 13 | ) 14 | 15 | IF "%NIM_ARCH%" == "" ( 16 | ECHO NIM_ARCH variable is not set 17 | EXIT /B 0 18 | ) 19 | 20 | IF NOT EXIST "%CD%\%NIM_DIR%\bin\nim.exe" ( 21 | CALL :BUILD_NIM 22 | ) ELSE ( 23 | CALL :USE_NIM 24 | ) 25 | EXIT /B 0 26 | 27 | :BUILD_NIM 28 | ECHO Building Nim [%NIM_BRANCH%] (%NIM_ARCH%) in %NIM_DIR% 29 | git clone https://github.com/nim-lang/Nim.git "%CD%\%NIM_DIR%" 30 | CD "%CD%\%NIM_DIR%" 31 | IF NOT "%NIM_BRANCH%" == "devel" ( 32 | git checkout "tags/%NIM_BRANCH%" -b "%NIM_BRANCH%" 33 | ) ELSE ( 34 | git checkout devel 35 | ) 36 | git clone --depth 1 https://github.com/nim-lang/csources_v1 37 | CD csources_v1 38 | 39 | IF "%NIM_ARCH%" == "amd64" (CALL build64.bat) ELSE (CALL build32.bat) 40 | CD .. 41 | ECHO CSOURCES NIM DONE 42 | bin\nim c --skipParentCfg -d:release koch 43 | koch boot -d:release --skipParentCfg 44 | koch nimble --skipParentCfg 45 | CD .. 46 | EXIT /B 0 47 | 48 | :USE_NIM 49 | CD "%CD%\%NIM_DIR%" 50 | FOR /F "tokens=3" %%I IN ('git status ^| head -1') DO SET GITBRANCH=%%I 51 | ECHO Found Nim [%GITBRANCH%] in %NIM_DIR% 52 | IF "%GITBRANCH%" == "%NIM_BRANCH%" ( 53 | ECHO Using Nim [%GITBRANCH%] in %NIM_DIR% 54 | CD .. 55 | ) ELSE ( 56 | CD .. 57 | RMDIR /S /Q "%NIM_DIR%" 58 | CALL :BUILD_NIM 59 | ) 60 | 61 | EXIT /B 0 62 | -------------------------------------------------------------------------------- /tests/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -z "${NIM_DIR}" ]]; then 4 | echo "NIM_DIR variable is not set" 5 | exit 1 6 | fi 7 | 8 | if [[ -z "${NIM_BRANCH}" ]]; then 9 | echo "NIM_BRANCH variable is not set" 10 | exit 1 11 | fi 12 | 13 | function build_nim { 14 | echo "Building Nim [${NIM_BRANCH}] in ${NIM_DIR}" 15 | git clone https://github.com/nim-lang/Nim.git ${NIM_DIR} 16 | cd "${NIM_DIR}" 17 | if [ "${NIM_BRANCH}" = "devel" ]; then 18 | git checkout devel 19 | else 20 | git checkout "tags/${NIM_BRANCH}" -b "${NIM_BRANCH}" 21 | fi 22 | git clone --depth 1 https://github.com/nim-lang/csources_v1 23 | cd csources_v1 && sh build.sh 24 | cd .. 25 | bin/nim c -d:release koch 26 | ./koch boot -d:release 27 | ./koch nimble -d:release 28 | cd .. 29 | } 30 | 31 | function use_nim { 32 | cd "${NIM_DIR}" 33 | GITBRANCH=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p') 34 | echo "Found Nim [${GITBRANCH}] in ${NIM_DIR}" 35 | if [ "${GITBRANCH}" = "${NIM_BRANCH}" ]; then 36 | echo "Using Nim [${GITBRANCH}] in ${NIM_DIR}" 37 | cd .. 38 | else 39 | cd .. 40 | rm -rf "${NIM_DIR}" 41 | build_nim 42 | fi 43 | } 44 | 45 | if [ ! -e "${NIM_DIR}/bin/nim" ]; then 46 | build_nim 47 | else 48 | use_nim 49 | fi 50 | --------------------------------------------------------------------------------