Merge branch 'devel' of https://github.com/nim-lang/Nim into devel
This commit is contained in:
commit
e11d582fa1
14 changed files with 278 additions and 99 deletions
25
appveyor.yml
25
appveyor.yml
|
|
@ -1,14 +1,11 @@
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
DLLS_URL: https://nim-lang.org/download/dlls.zip
|
||||||
|
DLLS_ARCHIVE: dlls.zip
|
||||||
MINGW_DIR: mingw64
|
MINGW_DIR: mingw64
|
||||||
MINGW_URL: https://nim-lang.org/download/mingw64-6.3.0.7z
|
MINGW_URL: https://nim-lang.org/download/mingw64-6.3.0.7z
|
||||||
MINGW_ARCHIVE: mingw64-6.3.0.7z
|
MINGW_ARCHIVE: mingw64-6.3.0.7z
|
||||||
OPENBLAS_URL: https://sourceforge.net/projects/openblas/files/v0.3.5/OpenBLAS%200.3.5%20version.zip/download
|
|
||||||
OPENBLAS_ARCHIVE: OpenBLAS-0.3.5.zip
|
|
||||||
SQLITE_URL: http://www.sqlite.org/2017/sqlite-dll-win64-x64-3160200.zip
|
|
||||||
SQLITE_ARCHIVE: sqlite-dll-win64-x64-3160200.zip
|
|
||||||
platform: x64
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
- NIM_TEST_PACKAGES: false
|
- NIM_TEST_PACKAGES: false
|
||||||
|
|
@ -16,8 +13,7 @@ environment:
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- '%MINGW_ARCHIVE%'
|
- '%MINGW_ARCHIVE%'
|
||||||
- '%SQLITE_ARCHIVE%'
|
- '%DLLS_ARCHIVE%'
|
||||||
- '%OPENBLAS_ARCHIVE%'
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
#allow_failures:
|
#allow_failures:
|
||||||
|
|
@ -26,23 +22,14 @@ matrix:
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- ps: Install-Product node 8 # node 8 or later is required to test js async stuff
|
- ps: Install-Product node 8 # node 8 or later is required to test js async stuff
|
||||||
- MKDIR %CD%\DIST
|
- IF not exist "%DLLS_ARCHIVE%" appveyor DownloadFile "%DLLS_URL%" -FileName "%DLLS_ARCHIVE%"
|
||||||
- MKDIR %CD%\DIST\PCRE
|
- 7z x -y "%DLLS_ARCHIVE%" -o"%CD%\BIN"> nul
|
||||||
- nuget install pcre -Verbosity quiet -Version 8.33.0.1 -OutputDirectory %CD%\DIST\PCRE
|
|
||||||
- IF not exist "%SQLITE_ARCHIVE%" appveyor DownloadFile "%SQLITE_URL%" -FileName "%SQLITE_ARCHIVE%"
|
|
||||||
- 7z x -y "%SQLITE_ARCHIVE%" -o"%CD%\DIST"> nul
|
|
||||||
- IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
|
- IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
|
||||||
- 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\DIST"> nul
|
- 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\DIST"> nul
|
||||||
- IF not exist "%OPENBLAS_ARCHIVE%" appveyor DownloadFile "%OPENBLAS_URL%" -FileName "%OPENBLAS_ARCHIVE%"
|
|
||||||
- 7z x -y "%OPENBLAS_ARCHIVE%" -o"%CD%\DIST"> nul
|
|
||||||
- SET PATH=%CD%\DIST\%MINGW_DIR%\BIN;%CD%\BIN;%PATH%
|
- SET PATH=%CD%\DIST\%MINGW_DIR%\BIN;%CD%\BIN;%PATH%
|
||||||
- IF "%PLATFORM%" == "x64" ( copy C:\OpenSSL-Win64\libeay32.dll %CD%\BIN\libeay64.dll & copy C:\OpenSSL-Win64\libeay32.dll %CD%\BIN\libeay32.dll & copy C:\OpenSSL-Win64\libssl32.dll %CD%\BIN\libssl64.dll & copy C:\OpenSSL-Win64\libssl32.dll %CD%\BIN\libssl32.dll )
|
|
||||||
ELSE ( copy C:\OpenSSL-Win32\libeay32.dll %CD%\BIN\libeay32.dll & copy C:\OpenSSL-Win32\libssl32.dll %CD%\BIN\libssl32.dll )
|
|
||||||
- IF "%PLATFORM%" == "x64" ( copy %CD%\DIST\sqlite3.dll %CD%\BIN\sqlite3_64.dll ) ELSE ( copy %CD%\DIST\sqlite3.dll %CD%\BIN\sqlite3_32.dll )
|
|
||||||
- IF "%PLATFORM%" == "x64" ( copy %CD%\DIST\PCRE\pcre.redist.8.33.0.1\build\native\bin\v100\x64\Release\dynamic\utf8\pcre8.dll %CD%\bin\pcre64.dll ) ELSE ( copy %CD%\DIST\PCRE\pcre.redist.8.33.0.1\build\native\bin\v100\Win32\Release\dynamic\utf8\pcre8.dll %CD%\bin\pcre32.dll )
|
|
||||||
- git clone --depth 1 https://github.com/nim-lang/csources
|
- git clone --depth 1 https://github.com/nim-lang/csources
|
||||||
- cd csources
|
- cd csources
|
||||||
- IF "%PLATFORM%" == "x64" ( build64.bat ) else ( build.bat )
|
- build64.bat
|
||||||
- cd ..
|
- cd ..
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,7 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
|
||||||
if endsInAlpha(em):
|
if endsInAlpha(em):
|
||||||
wrSpace em
|
wrSpace em
|
||||||
elif not em.inquote and not endsInWhite(em) and
|
elif not em.inquote and not endsInWhite(em) and
|
||||||
em.lastTok notin openPars and not em.lastTokWasTerse:
|
em.lastTok notin (openPars+{tkOpr, tkDotDot}) and not em.lastTokWasTerse:
|
||||||
#and tok.tokType in oprSet
|
#and tok.tokType in oprSet
|
||||||
wrSpace em
|
wrSpace em
|
||||||
|
|
||||||
|
|
@ -392,8 +392,8 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
|
||||||
wr(em, TokTypeToStr[tok.tokType], ltOther)
|
wr(em, TokTypeToStr[tok.tokType], ltOther)
|
||||||
if not em.inquote: wrSpace(em)
|
if not em.inquote: wrSpace(em)
|
||||||
of tkOpr, tkDotDot:
|
of tkOpr, tkDotDot:
|
||||||
if ((tok.strongSpaceA == 0 and tok.strongSpaceB == 0) or em.inquote) and
|
if em.inquote or ((tok.strongSpaceA == 0 and tok.strongSpaceB == 0) and
|
||||||
tok.ident.s notin ["<", ">", "<=", ">=", "==", "!="]:
|
tok.ident.s notin ["<", ">", "<=", ">=", "==", "!="]):
|
||||||
# bug #9504: remember to not spacify a keyword:
|
# bug #9504: remember to not spacify a keyword:
|
||||||
lastTokWasTerse = true
|
lastTokWasTerse = true
|
||||||
# if not surrounded by whitespace, don't produce any whitespace either:
|
# if not surrounded by whitespace, don't produce any whitespace either:
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ proc isNimIdentifier*(s: string): bool =
|
||||||
inc(i)
|
inc(i)
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc tokToStr*(tok: TToken): string =
|
proc `$`*(tok: TToken): string =
|
||||||
case tok.tokType
|
case tok.tokType
|
||||||
of tkIntLit..tkInt64Lit: result = $tok.iNumber
|
of tkIntLit..tkInt64Lit: result = $tok.iNumber
|
||||||
of tkFloatLit..tkFloat64Lit: result = $tok.fNumber
|
of tkFloatLit..tkFloat64Lit: result = $tok.fNumber
|
||||||
|
|
@ -188,11 +188,11 @@ proc tokToStr*(tok: TToken): string =
|
||||||
|
|
||||||
proc prettyTok*(tok: TToken): string =
|
proc prettyTok*(tok: TToken): string =
|
||||||
if isKeyword(tok.tokType): result = "keyword " & tok.ident.s
|
if isKeyword(tok.tokType): result = "keyword " & tok.ident.s
|
||||||
else: result = tokToStr(tok)
|
else: result = $tok
|
||||||
|
|
||||||
proc printTok*(conf: ConfigRef; tok: TToken) =
|
proc printTok*(conf: ConfigRef; tok: TToken) =
|
||||||
msgWriteln(conf, $tok.line & ":" & $tok.col & "\t" &
|
msgWriteln(conf, $tok.line & ":" & $tok.col & "\t" &
|
||||||
TokTypeToStr[tok.tokType] & " " & tokToStr(tok))
|
TokTypeToStr[tok.tokType] & " " & $tok)
|
||||||
|
|
||||||
proc initToken*(L: var TToken) =
|
proc initToken*(L: var TToken) =
|
||||||
L.tokType = tkInvalid
|
L.tokType = tkInvalid
|
||||||
|
|
|
||||||
|
|
@ -123,31 +123,31 @@ proc parseDirective(L: var TLexer, tok: var TToken; config: ConfigRef; condStack
|
||||||
of wEnd: doEnd(L, tok, condStack)
|
of wEnd: doEnd(L, tok, condStack)
|
||||||
of wWrite:
|
of wWrite:
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
msgs.msgWriteln(config, strtabs.`%`(tokToStr(tok), config.configVars,
|
msgs.msgWriteln(config, strtabs.`%`($tok, config.configVars,
|
||||||
{useEnvironment, useKey}))
|
{useEnvironment, useKey}))
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
else:
|
else:
|
||||||
case tok.ident.s.normalize
|
case tok.ident.s.normalize
|
||||||
of "putenv":
|
of "putenv":
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
var key = tokToStr(tok)
|
var key = $tok
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
os.putEnv(key, tokToStr(tok))
|
os.putEnv(key, $tok)
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
of "prependenv":
|
of "prependenv":
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
var key = tokToStr(tok)
|
var key = $tok
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
os.putEnv(key, tokToStr(tok) & os.getEnv(key))
|
os.putEnv(key, $tok & os.getEnv(key))
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
of "appendenv":
|
of "appendenv":
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
var key = tokToStr(tok)
|
var key = $tok
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
os.putEnv(key, os.getEnv(key) & tokToStr(tok))
|
os.putEnv(key, os.getEnv(key) & $tok)
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
else:
|
else:
|
||||||
lexMessage(L, errGenerated, "invalid directive: '$1'" % tokToStr(tok))
|
lexMessage(L, errGenerated, "invalid directive: '$1'" % $tok)
|
||||||
|
|
||||||
proc confTok(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) =
|
proc confTok(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) =
|
||||||
ppGetTok(L, tok)
|
ppGetTok(L, tok)
|
||||||
|
|
@ -156,7 +156,7 @@ proc confTok(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var s
|
||||||
|
|
||||||
proc checkSymbol(L: TLexer, tok: TToken) =
|
proc checkSymbol(L: TLexer, tok: TToken) =
|
||||||
if tok.tokType notin {tkSymbol..tkInt64Lit, tkStrLit..tkTripleStrLit}:
|
if tok.tokType notin {tkSymbol..tkInt64Lit, tkStrLit..tkTripleStrLit}:
|
||||||
lexMessage(L, errGenerated, "expected identifier, but got: " & tokToStr(tok))
|
lexMessage(L, errGenerated, "expected identifier, but got: " & $tok)
|
||||||
|
|
||||||
proc parseAssignment(L: var TLexer, tok: var TToken;
|
proc parseAssignment(L: var TLexer, tok: var TToken;
|
||||||
config: ConfigRef; condStack: var seq[bool]) =
|
config: ConfigRef; condStack: var seq[bool]) =
|
||||||
|
|
@ -164,21 +164,21 @@ proc parseAssignment(L: var TLexer, tok: var TToken;
|
||||||
confTok(L, tok, config, condStack) # skip unnecessary prefix
|
confTok(L, tok, config, condStack) # skip unnecessary prefix
|
||||||
var info = getLineInfo(L, tok) # save for later in case of an error
|
var info = getLineInfo(L, tok) # save for later in case of an error
|
||||||
checkSymbol(L, tok)
|
checkSymbol(L, tok)
|
||||||
var s = tokToStr(tok)
|
var s = $tok
|
||||||
confTok(L, tok, config, condStack) # skip symbol
|
confTok(L, tok, config, condStack) # skip symbol
|
||||||
var val = ""
|
var val = ""
|
||||||
while tok.tokType == tkDot:
|
while tok.tokType == tkDot:
|
||||||
add(s, '.')
|
add(s, '.')
|
||||||
confTok(L, tok, config, condStack)
|
confTok(L, tok, config, condStack)
|
||||||
checkSymbol(L, tok)
|
checkSymbol(L, tok)
|
||||||
add(s, tokToStr(tok))
|
add(s, $tok)
|
||||||
confTok(L, tok, config, condStack)
|
confTok(L, tok, config, condStack)
|
||||||
if tok.tokType == tkBracketLe:
|
if tok.tokType == tkBracketLe:
|
||||||
# BUGFIX: val, not s!
|
# BUGFIX: val, not s!
|
||||||
confTok(L, tok, config, condStack)
|
confTok(L, tok, config, condStack)
|
||||||
checkSymbol(L, tok)
|
checkSymbol(L, tok)
|
||||||
add(val, '[')
|
add(val, '[')
|
||||||
add(val, tokToStr(tok))
|
add(val, $tok)
|
||||||
confTok(L, tok, config, condStack)
|
confTok(L, tok, config, condStack)
|
||||||
if tok.tokType == tkBracketRi: confTok(L, tok, config, condStack)
|
if tok.tokType == tkBracketRi: confTok(L, tok, config, condStack)
|
||||||
else: lexMessage(L, errGenerated, "expected closing ']'")
|
else: lexMessage(L, errGenerated, "expected closing ']'")
|
||||||
|
|
@ -188,12 +188,12 @@ proc parseAssignment(L: var TLexer, tok: var TToken;
|
||||||
if len(val) > 0: add(val, ':')
|
if len(val) > 0: add(val, ':')
|
||||||
confTok(L, tok, config, condStack) # skip ':' or '=' or '%'
|
confTok(L, tok, config, condStack) # skip ':' or '=' or '%'
|
||||||
checkSymbol(L, tok)
|
checkSymbol(L, tok)
|
||||||
add(val, tokToStr(tok))
|
add(val, $tok)
|
||||||
confTok(L, tok, config, condStack) # skip symbol
|
confTok(L, tok, config, condStack) # skip symbol
|
||||||
while tok.ident != nil and tok.ident.s == "&":
|
while tok.ident != nil and tok.ident.s == "&":
|
||||||
confTok(L, tok, config, condStack)
|
confTok(L, tok, config, condStack)
|
||||||
checkSymbol(L, tok)
|
checkSymbol(L, tok)
|
||||||
add(val, tokToStr(tok))
|
add(val, $tok)
|
||||||
confTok(L, tok, config, condStack)
|
confTok(L, tok, config, condStack)
|
||||||
if percent:
|
if percent:
|
||||||
processSwitch(s, strtabs.`%`(val, config.configVars,
|
processSwitch(s, strtabs.`%`(val, config.configVars,
|
||||||
|
|
|
||||||
|
|
@ -343,13 +343,13 @@ proc parseSymbol(p: var TParser, mode = smNormal): PNode =
|
||||||
var accm = ""
|
var accm = ""
|
||||||
while p.tok.tokType in {tkOpr, tkDot, tkDotDot, tkEquals,
|
while p.tok.tokType in {tkOpr, tkDot, tkDotDot, tkEquals,
|
||||||
tkParLe..tkParDotRi}:
|
tkParLe..tkParDotRi}:
|
||||||
accm.add(tokToStr(p.tok))
|
accm.add($p.tok)
|
||||||
getTok(p)
|
getTok(p)
|
||||||
let node = newNodeI(nkIdent, lineinfo)
|
let node = newNodeI(nkIdent, lineinfo)
|
||||||
node.ident = p.lex.cache.getIdent(accm)
|
node.ident = p.lex.cache.getIdent(accm)
|
||||||
result.add(node)
|
result.add(node)
|
||||||
of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCharLit:
|
of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCharLit:
|
||||||
result.add(newIdentNodeP(p.lex.cache.getIdent(tokToStr(p.tok)), p))
|
result.add(newIdentNodeP(p.lex.cache.getIdent($p.tok), p))
|
||||||
getTok(p)
|
getTok(p)
|
||||||
else:
|
else:
|
||||||
parMessage(p, errIdentifierExpected, p.tok)
|
parMessage(p, errIdentifierExpected, p.tok)
|
||||||
|
|
@ -903,6 +903,8 @@ proc parsePragma(p: var TParser): PNode =
|
||||||
#| pragma = '{.' optInd (exprColonExpr comma?)* optPar ('.}' | '}')
|
#| pragma = '{.' optInd (exprColonExpr comma?)* optPar ('.}' | '}')
|
||||||
result = newNodeP(nkPragma, p)
|
result = newNodeP(nkPragma, p)
|
||||||
inc p.inPragma
|
inc p.inPragma
|
||||||
|
when defined(nimpretty):
|
||||||
|
inc p.em.keepIndents
|
||||||
getTok(p)
|
getTok(p)
|
||||||
optInd(p, result)
|
optInd(p, result)
|
||||||
while p.tok.tokType notin {tkCurlyDotRi, tkCurlyRi, tkEof}:
|
while p.tok.tokType notin {tkCurlyDotRi, tkCurlyRi, tkEof}:
|
||||||
|
|
@ -921,6 +923,8 @@ proc parsePragma(p: var TParser): PNode =
|
||||||
else:
|
else:
|
||||||
parMessage(p, "expected '.}'")
|
parMessage(p, "expected '.}'")
|
||||||
dec p.inPragma
|
dec p.inPragma
|
||||||
|
when defined(nimpretty):
|
||||||
|
dec p.em.keepIndents
|
||||||
|
|
||||||
proc identVis(p: var TParser; allowDot=false): PNode =
|
proc identVis(p: var TParser; allowDot=false): PNode =
|
||||||
#| identVis = symbol opr? # postfix position
|
#| identVis = symbol opr? # postfix position
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ To compile a release version use::
|
||||||
nim c -d:release greetings.nim
|
nim c -d:release greetings.nim
|
||||||
|
|
||||||
By default the Nim compiler generates a large amount of runtime checks
|
By default the Nim compiler generates a large amount of runtime checks
|
||||||
aiming for your debugging pleasure. With ``-d:release`` these checks are
|
aiming for your debugging pleasure. With ``-d:release`` some checks are
|
||||||
`turned off and optimizations are turned on
|
`turned off and optimizations are turned on
|
||||||
<nimc.html#compiler-usage-compile-time-symbols>`_.
|
<nimc.html#compiler-usage-compile-time-symbols>`_.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -568,7 +568,7 @@ when defined(windows) or defined(nimdoc):
|
||||||
if flags.isDisconnectionError(errcode):
|
if flags.isDisconnectionError(errcode):
|
||||||
retFuture.complete()
|
retFuture.complete()
|
||||||
else:
|
else:
|
||||||
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
|
retFuture.fail(newOSError(errcode))
|
||||||
)
|
)
|
||||||
|
|
||||||
let ret = WSASend(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
|
let ret = WSASend(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
|
||||||
|
|
@ -1134,11 +1134,6 @@ else:
|
||||||
var data = newAsyncData()
|
var data = newAsyncData()
|
||||||
p.selector.registerHandle(fd.SocketHandle, {}, data)
|
p.selector.registerHandle(fd.SocketHandle, {}, data)
|
||||||
|
|
||||||
proc closeSocket*(sock: AsyncFD) =
|
|
||||||
let disp = getGlobalDispatcher()
|
|
||||||
disp.selector.unregister(sock.SocketHandle)
|
|
||||||
sock.SocketHandle.close()
|
|
||||||
|
|
||||||
proc unregister*(fd: AsyncFD) =
|
proc unregister*(fd: AsyncFD) =
|
||||||
getGlobalDispatcher().selector.unregister(fd.SocketHandle)
|
getGlobalDispatcher().selector.unregister(fd.SocketHandle)
|
||||||
|
|
||||||
|
|
@ -1174,7 +1169,9 @@ else:
|
||||||
let p = getGlobalDispatcher()
|
let p = getGlobalDispatcher()
|
||||||
not p.selector.isEmpty() or p.timers.len != 0 or p.callbacks.len != 0
|
not p.selector.isEmpty() or p.timers.len != 0 or p.callbacks.len != 0
|
||||||
|
|
||||||
template processBasicCallbacks(ident, rwlist: untyped) =
|
proc processBasicCallbacks(
|
||||||
|
fd: AsyncFD, event: Event
|
||||||
|
): tuple[readCbListCount, writeCbListCount: int] =
|
||||||
# Process pending descriptor and AsyncEvent callbacks.
|
# Process pending descriptor and AsyncEvent callbacks.
|
||||||
#
|
#
|
||||||
# Invoke every callback stored in `rwlist`, until one
|
# Invoke every callback stored in `rwlist`, until one
|
||||||
|
|
@ -1187,32 +1184,46 @@ else:
|
||||||
# or it can be possible to fall into endless cycle.
|
# or it can be possible to fall into endless cycle.
|
||||||
var curList: seq[Callback]
|
var curList: seq[Callback]
|
||||||
|
|
||||||
withData(p.selector, ident, adata) do:
|
let selector = getGlobalDispatcher().selector
|
||||||
shallowCopy(curList, adata.rwlist)
|
withData(selector, fd.int, fdData):
|
||||||
adata.rwlist = newSeqOfCap[Callback](InitCallbackListSize)
|
case event
|
||||||
|
of Event.Read:
|
||||||
|
shallowCopy(curList, fdData.readList)
|
||||||
|
fdData.readList = newSeqOfCap[Callback](InitCallbackListSize)
|
||||||
|
of Event.Write:
|
||||||
|
shallowCopy(curList, fdData.writeList)
|
||||||
|
fdData.writeList = newSeqOfCap[Callback](InitCallbackListSize)
|
||||||
|
else:
|
||||||
|
assert false, "Cannot process callbacks for " & $event
|
||||||
|
|
||||||
let newLength = max(len(curList), InitCallbackListSize)
|
let newLength = max(len(curList), InitCallbackListSize)
|
||||||
var newList = newSeqOfCap[Callback](newLength)
|
var newList = newSeqOfCap[Callback](newLength)
|
||||||
|
|
||||||
for cb in curList:
|
for cb in curList:
|
||||||
if len(newList) > 0:
|
if not cb(fd):
|
||||||
# A callback has already returned with EAGAIN, don't call any others
|
|
||||||
# until next `poll`.
|
|
||||||
newList.add(cb)
|
|
||||||
else:
|
|
||||||
if not cb(fd.AsyncFD):
|
|
||||||
# Callback wants to be called again.
|
# Callback wants to be called again.
|
||||||
newList.add(cb)
|
newList.add(cb)
|
||||||
|
# This callback has returned with EAGAIN, so we don't need to
|
||||||
|
# call any other callbacks as they are all waiting for the same event
|
||||||
|
# on the same fd.
|
||||||
|
break
|
||||||
|
|
||||||
withData(p.selector, ident, adata) do:
|
withData(selector, fd.int, fdData) do:
|
||||||
# descriptor still present in queue.
|
# Descriptor is still present in the queue.
|
||||||
adata.rwlist = newList & adata.rwlist
|
case event
|
||||||
rLength = len(adata.readList)
|
of Event.Read:
|
||||||
wLength = len(adata.writeList)
|
fdData.readList = newList & fdData.readList
|
||||||
|
of Event.Write:
|
||||||
|
fdData.writeList = newList & fdData.writeList
|
||||||
|
else:
|
||||||
|
assert false, "Cannot process callbacks for " & $event
|
||||||
|
|
||||||
|
result.readCbListCount = len(fdData.readList)
|
||||||
|
result.writeCbListCount = len(fdData.writeList)
|
||||||
do:
|
do:
|
||||||
# descriptor was unregistered in callback via `unregister()`.
|
# Descriptor was unregistered in callback via `unregister()`.
|
||||||
rLength = -1
|
result.readCbListCount = -1
|
||||||
wLength = -1
|
result.writeCbListCount = -1
|
||||||
|
|
||||||
template processCustomCallbacks(ident: untyped) =
|
template processCustomCallbacks(ident: untyped) =
|
||||||
# Process pending custom event callbacks. Custom events are
|
# Process pending custom event callbacks. Custom events are
|
||||||
|
|
@ -1221,7 +1232,7 @@ else:
|
||||||
# so there is no need to iterate over list.
|
# so there is no need to iterate over list.
|
||||||
var curList: seq[Callback]
|
var curList: seq[Callback]
|
||||||
|
|
||||||
withData(p.selector, ident, adata) do:
|
withData(p.selector, ident.int, adata) do:
|
||||||
shallowCopy(curList, adata.readList)
|
shallowCopy(curList, adata.readList)
|
||||||
adata.readList = newSeqOfCap[Callback](InitCallbackListSize)
|
adata.readList = newSeqOfCap[Callback](InitCallbackListSize)
|
||||||
|
|
||||||
|
|
@ -1232,16 +1243,33 @@ else:
|
||||||
if not cb(fd.AsyncFD):
|
if not cb(fd.AsyncFD):
|
||||||
newList.add(cb)
|
newList.add(cb)
|
||||||
|
|
||||||
withData(p.selector, ident, adata) do:
|
withData(p.selector, ident.int, adata) do:
|
||||||
# descriptor still present in queue.
|
# descriptor still present in queue.
|
||||||
adata.readList = newList & adata.readList
|
adata.readList = newList & adata.readList
|
||||||
if len(adata.readList) == 0:
|
if len(adata.readList) == 0:
|
||||||
# if no callbacks registered with descriptor, unregister it.
|
# if no callbacks registered with descriptor, unregister it.
|
||||||
p.selector.unregister(fd)
|
p.selector.unregister(fd.int)
|
||||||
do:
|
do:
|
||||||
# descriptor was unregistered in callback via `unregister()`.
|
# descriptor was unregistered in callback via `unregister()`.
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
proc closeSocket*(sock: AsyncFD) =
|
||||||
|
let selector = getGlobalDispatcher().selector
|
||||||
|
if sock.SocketHandle notin selector:
|
||||||
|
raise newException(ValueError, "File descriptor not registered.")
|
||||||
|
|
||||||
|
let data = selector.getData(sock.SocketHandle)
|
||||||
|
sock.unregister()
|
||||||
|
sock.SocketHandle.close()
|
||||||
|
# We need to unblock the read and write callbacks which could still be
|
||||||
|
# waiting for the socket to become readable and/or writeable.
|
||||||
|
for cb in data.readList & data.writeList:
|
||||||
|
if not cb(sock):
|
||||||
|
raise newException(
|
||||||
|
ValueError, "Expecting async operations to stop when fd has closed."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
proc runOnce(timeout = 500): bool =
|
proc runOnce(timeout = 500): bool =
|
||||||
let p = getGlobalDispatcher()
|
let p = getGlobalDispatcher()
|
||||||
when ioselSupportedPlatform:
|
when ioselSupportedPlatform:
|
||||||
|
|
@ -1257,40 +1285,41 @@ else:
|
||||||
let nextTimer = processTimers(p, result)
|
let nextTimer = processTimers(p, result)
|
||||||
var count = p.selector.selectInto(adjustTimeout(timeout, nextTimer), keys)
|
var count = p.selector.selectInto(adjustTimeout(timeout, nextTimer), keys)
|
||||||
for i in 0..<count:
|
for i in 0..<count:
|
||||||
var custom = false
|
let fd = keys[i].fd.AsyncFD
|
||||||
let fd = keys[i].fd
|
|
||||||
let events = keys[i].events
|
let events = keys[i].events
|
||||||
var rLength = 0 # len(data.readList) after callback
|
var (readCbListCount, writeCbListCount) = (0, 0)
|
||||||
var wLength = 0 # len(data.writeList) after callback
|
|
||||||
|
|
||||||
if Event.Read in events or events == {Event.Error}:
|
if Event.Read in events or events == {Event.Error}:
|
||||||
processBasicCallbacks(fd, readList)
|
(readCbListCount, writeCbListCount) =
|
||||||
|
processBasicCallbacks(fd, Event.Read)
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
if Event.Write in events or events == {Event.Error}:
|
if Event.Write in events or events == {Event.Error}:
|
||||||
processBasicCallbacks(fd, writeList)
|
(readCbListCount, writeCbListCount) =
|
||||||
|
processBasicCallbacks(fd, Event.Write)
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
|
var isCustomEvent = false
|
||||||
if Event.User in events:
|
if Event.User in events:
|
||||||
processBasicCallbacks(fd, readList)
|
(readCbListCount, writeCbListCount) =
|
||||||
custom = true
|
processBasicCallbacks(fd, Event.Read)
|
||||||
if rLength == 0:
|
isCustomEvent = true
|
||||||
p.selector.unregister(fd)
|
if readCbListCount == 0:
|
||||||
|
p.selector.unregister(fd.int)
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
when ioselSupportedPlatform:
|
when ioselSupportedPlatform:
|
||||||
if (customSet * events) != {}:
|
if (customSet * events) != {}:
|
||||||
custom = true
|
isCustomEvent = true
|
||||||
processCustomCallbacks(fd)
|
processCustomCallbacks(fd)
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
# because state `data` can be modified in callback we need to update
|
# because state `data` can be modified in callback we need to update
|
||||||
# descriptor events with currently registered callbacks.
|
# descriptor events with currently registered callbacks.
|
||||||
if not custom:
|
if not isCustomEvent and (readCbListCount != -1 and writeCbListCount != -1):
|
||||||
var newEvents: set[Event] = {}
|
var newEvents: set[Event] = {}
|
||||||
if rLength != -1 and wLength != -1:
|
if readCbListCount > 0: incl(newEvents, Event.Read)
|
||||||
if rLength > 0: incl(newEvents, Event.Read)
|
if writeCbListCount > 0: incl(newEvents, Event.Write)
|
||||||
if wLength > 0: incl(newEvents, Event.Write)
|
|
||||||
p.selector.updateHandle(SocketHandle(fd), newEvents)
|
p.selector.updateHandle(SocketHandle(fd), newEvents)
|
||||||
|
|
||||||
# Timer processing.
|
# Timer processing.
|
||||||
|
|
@ -1370,7 +1399,7 @@ else:
|
||||||
if flags.isDisconnectionError(lastError):
|
if flags.isDisconnectionError(lastError):
|
||||||
retFuture.complete()
|
retFuture.complete()
|
||||||
else:
|
else:
|
||||||
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
|
retFuture.fail(newOSError(lastError))
|
||||||
else:
|
else:
|
||||||
result = false # We still want this callback to be called.
|
result = false # We still want this callback to be called.
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -393,11 +393,13 @@ proc asyncCheck*[T](future: Future[T]) =
|
||||||
## This should be used instead of ``discard`` to discard void futures,
|
## This should be used instead of ``discard`` to discard void futures,
|
||||||
## or use ``waitFor`` if you need to wait for the future's completion.
|
## or use ``waitFor`` if you need to wait for the future's completion.
|
||||||
assert(not future.isNil, "Future is nil")
|
assert(not future.isNil, "Future is nil")
|
||||||
future.callback =
|
# TODO: We can likely look at the stack trace here and inject the location
|
||||||
proc () =
|
# where the `asyncCheck` was called to give a better error stack message.
|
||||||
|
proc asyncCheckCallback() =
|
||||||
if future.failed:
|
if future.failed:
|
||||||
injectStacktrace(future)
|
injectStacktrace(future)
|
||||||
raise future.error
|
raise future.error
|
||||||
|
future.callback = asyncCheckCallback
|
||||||
|
|
||||||
proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
|
proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
|
||||||
## Returns a future which will complete once both ``fut1`` and ``fut2``
|
## Returns a future which will complete once both ``fut1`` and ``fut2``
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,10 @@ proc osErrorMsg*(errorCode: OSErrorCode): string =
|
||||||
if errorCode != OSErrorCode(0'i32):
|
if errorCode != OSErrorCode(0'i32):
|
||||||
result = $c_strerror(errorCode.int32)
|
result = $c_strerror(errorCode.int32)
|
||||||
|
|
||||||
proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
|
proc newOSError*(
|
||||||
## Raises an `OSError exception <system.html#OSError>`_.
|
errorCode: OSErrorCode, additionalInfo = ""
|
||||||
|
): owned(ref OSError) {.noinline.} =
|
||||||
|
## Creates a new `OSError exception <system.html#OSError>`_.
|
||||||
##
|
##
|
||||||
## The ``errorCode`` will determine the
|
## The ``errorCode`` will determine the
|
||||||
## message, `osErrorMsg proc <#osErrorMsg,OSErrorCode>`_ will be used
|
## message, `osErrorMsg proc <#osErrorMsg,OSErrorCode>`_ will be used
|
||||||
|
|
@ -82,7 +84,14 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
|
||||||
e.msg.addQuoted additionalInfo
|
e.msg.addQuoted additionalInfo
|
||||||
if e.msg == "":
|
if e.msg == "":
|
||||||
e.msg = "unknown OS error"
|
e.msg = "unknown OS error"
|
||||||
raise e
|
return e
|
||||||
|
|
||||||
|
proc raiseOSError*(errorCode: OSErrorCode, additionalInfo = "") {.noinline.} =
|
||||||
|
## Raises an `OSError exception <system.html#OSError>`_.
|
||||||
|
##
|
||||||
|
## Read the description of the `newOSError proc <#newOSError,OSErrorCode,string>`_ to learn
|
||||||
|
## how the exception object is created.
|
||||||
|
raise newOSError(errorCode, additionalInfo)
|
||||||
|
|
||||||
{.push stackTrace:off.}
|
{.push stackTrace:off.}
|
||||||
proc osLastError*(): OSErrorCode {.sideEffect.} =
|
proc osLastError*(): OSErrorCode {.sideEffect.} =
|
||||||
|
|
|
||||||
|
|
@ -3775,6 +3775,9 @@ proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} =
|
||||||
## A shorthand for ``echo(errormsg); quit(errorcode)``.
|
## A shorthand for ``echo(errormsg); quit(errorcode)``.
|
||||||
when defined(nimscript) or defined(js) or (hostOS == "standalone"):
|
when defined(nimscript) or defined(js) or (hostOS == "standalone"):
|
||||||
echo errormsg
|
echo errormsg
|
||||||
|
else:
|
||||||
|
when nimvm:
|
||||||
|
echo errormsg
|
||||||
else:
|
else:
|
||||||
cstderr.rawWrite(errormsg)
|
cstderr.rawWrite(errormsg)
|
||||||
cstderr.rawWrite("\n")
|
cstderr.rawWrite("\n")
|
||||||
|
|
|
||||||
|
|
@ -409,3 +409,26 @@ type
|
||||||
ccFastCall, # fastcall (pass parameters in registers)
|
ccFastCall, # fastcall (pass parameters in registers)
|
||||||
ccClosure, # proc has a closure
|
ccClosure, # proc has a closure
|
||||||
ccNoConvention # needed for generating proper C procs sometimes
|
ccNoConvention # needed for generating proper C procs sometimes
|
||||||
|
|
||||||
|
|
||||||
|
proc isValid1*[A](s: HashSet[A]): bool {.deprecated:
|
||||||
|
"Deprecated since v0.20; sets are initialized by default".} =
|
||||||
|
## Returns `true` if the set has been initialized (with `initHashSet proc
|
||||||
|
## <#initHashSet,int>`_ or `init proc <#init,HashSet[A],int>`_).
|
||||||
|
result = s.data.len > 0
|
||||||
|
# bug #11468
|
||||||
|
|
||||||
|
assert $type(a) == "Option[system.int]"
|
||||||
|
foo(a, $type(b), c)
|
||||||
|
foo(type(b), c) # this is ok
|
||||||
|
|
||||||
|
proc `<`*[A](s, t: A): bool = discard
|
||||||
|
proc `==`*[A](s, t: HashSet[A]): bool = discard
|
||||||
|
proc `<=`*[A](s, t: HashSet[A]): bool = discard
|
||||||
|
|
||||||
|
# these are ok:
|
||||||
|
proc `$`*[A](s: HashSet[A]): string = discard
|
||||||
|
proc `*`*[A](s1, s2: HashSet[A]): HashSet[A] {.inline.} = discard
|
||||||
|
proc `-+-`*[A](s1, s2: HashSet[A]): HashSet[A] {.inline.} = discard
|
||||||
|
|
||||||
|
# bug #11470
|
||||||
|
|
|
||||||
|
|
@ -419,3 +419,26 @@ type
|
||||||
ccFastCall, # fastcall (pass parameters in registers)
|
ccFastCall, # fastcall (pass parameters in registers)
|
||||||
ccClosure, # proc has a closure
|
ccClosure, # proc has a closure
|
||||||
ccNoConvention # needed for generating proper C procs sometimes
|
ccNoConvention # needed for generating proper C procs sometimes
|
||||||
|
|
||||||
|
|
||||||
|
proc isValid1*[A](s: HashSet[A]): bool {.deprecated:
|
||||||
|
"Deprecated since v0.20; sets are initialized by default".} =
|
||||||
|
## Returns `true` if the set has been initialized (with `initHashSet proc
|
||||||
|
## <#initHashSet,int>`_ or `init proc <#init,HashSet[A],int>`_).
|
||||||
|
result = s.data.len > 0
|
||||||
|
# bug #11468
|
||||||
|
|
||||||
|
assert $type(a) == "Option[system.int]"
|
||||||
|
foo(a, $type(b), c)
|
||||||
|
foo(type(b), c) # this is ok
|
||||||
|
|
||||||
|
proc `<`*[A](s, t: A): bool = discard
|
||||||
|
proc `==`*[A](s, t: HashSet[A]): bool = discard
|
||||||
|
proc `<=`*[A](s, t: HashSet[A]): bool = discard
|
||||||
|
|
||||||
|
# these are ok:
|
||||||
|
proc `$`*[A](s: HashSet[A]): string = discard
|
||||||
|
proc `*`*[A](s1, s2: HashSet[A]): HashSet[A] {.inline.} = discard
|
||||||
|
proc `-+-`*[A](s1, s2: HashSet[A]): HashSet[A] {.inline.} = discard
|
||||||
|
|
||||||
|
# bug #11470
|
||||||
|
|
|
||||||
|
|
@ -224,8 +224,8 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode;
|
||||||
execute(cmd, file, dirtyfile, int(line), int(column), graph)
|
execute(cmd, file, dirtyfile, int(line), int(column), graph)
|
||||||
|
|
||||||
proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
|
proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
|
||||||
return_symbol = "return") =
|
returnSymbol = "return") =
|
||||||
let response = $convertSexp([newSSymbol(return_symbol), uid, s])
|
let response = $convertSexp([newSSymbol(returnSymbol), uid, s])
|
||||||
socket.send(toHex(len(response), 6))
|
socket.send(toHex(len(response), 6))
|
||||||
socket.send(response)
|
socket.send(response)
|
||||||
|
|
||||||
|
|
|
||||||
99
tests/async/tasyncclosestall.nim
Normal file
99
tests/async/tasyncclosestall.nim
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
discard """
|
||||||
|
outputsub: "send has errored. As expected. All good!"
|
||||||
|
exitcode: 0
|
||||||
|
"""
|
||||||
|
import asyncdispatch, asyncnet
|
||||||
|
|
||||||
|
when defined(windows):
|
||||||
|
from winlean import ERROR_NETNAME_DELETED
|
||||||
|
else:
|
||||||
|
from posix import EBADF
|
||||||
|
|
||||||
|
# This reproduces a case where a socket remains stuck waiting for writes
|
||||||
|
# even when the socket is closed.
|
||||||
|
const
|
||||||
|
port = Port(50726)
|
||||||
|
timeout = 5000
|
||||||
|
|
||||||
|
var sent = 0
|
||||||
|
|
||||||
|
proc keepSendingTo(c: AsyncSocket) {.async.} =
|
||||||
|
while true:
|
||||||
|
# This write will eventually get stuck because the client is not reading
|
||||||
|
# its messages.
|
||||||
|
let sendFut = c.send("Foobar" & $sent & "\n", flags = {})
|
||||||
|
if not await withTimeout(sendFut, timeout):
|
||||||
|
# The write is stuck. Let's simulate a scenario where the socket
|
||||||
|
# does not respond to PING messages, and we close it. The above future
|
||||||
|
# should complete after the socket is closed, not continue stalling.
|
||||||
|
echo("Socket has stalled, closing it")
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
let timeoutFut = withTimeout(sendFut, timeout)
|
||||||
|
yield timeoutFut
|
||||||
|
if timeoutFut.failed:
|
||||||
|
let errCode = ((ref OSError)(timeoutFut.error)).errorCode
|
||||||
|
# The behaviour differs across platforms. On Windows ERROR_NETNAME_DELETED
|
||||||
|
# is raised which we classif as a "diconnection error", hence we overwrite
|
||||||
|
# the flags above in the `send` call so that this error is raised.
|
||||||
|
#
|
||||||
|
# On Linux the EBADF error code is raised, this is because the socket
|
||||||
|
# is closed.
|
||||||
|
#
|
||||||
|
# This means that by default the behaviours will differ between Windows
|
||||||
|
# and Linux. I think this is fine though, it makes sense mainly because
|
||||||
|
# Windows doesn't use a IO readiness model. We can fix this later if
|
||||||
|
# necessary to reclassify ERROR_NETNAME_DELETED as not a "disconnection
|
||||||
|
# error" (TODO)
|
||||||
|
when defined(windows):
|
||||||
|
if errCode == ERROR_NETNAME_DELETED:
|
||||||
|
echo("send has errored. As expected. All good!")
|
||||||
|
quit(QuitSuccess)
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, "Test failed. Send failed with code " & $errCode)
|
||||||
|
else:
|
||||||
|
if errCode == EBADF:
|
||||||
|
echo("send has errored. As expected. All good!")
|
||||||
|
quit(QuitSuccess)
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, "Test failed. Send failed with code " & $errCode)
|
||||||
|
|
||||||
|
# The write shouldn't succeed and also shouldn't be stalled.
|
||||||
|
if timeoutFut.read():
|
||||||
|
raise newException(ValueError, "Test failed. Send was expected to fail.")
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, "Test failed. Send future is still stalled.")
|
||||||
|
sent.inc(1)
|
||||||
|
|
||||||
|
proc startClient() {.async.} =
|
||||||
|
let client = newAsyncSocket()
|
||||||
|
await client.connect("localhost", port)
|
||||||
|
echo("Connected")
|
||||||
|
|
||||||
|
let firstLine = await client.recvLine()
|
||||||
|
echo("Received first line as a client: ", firstLine)
|
||||||
|
echo("Now not reading anymore")
|
||||||
|
while true: await sleepAsync(1000)
|
||||||
|
|
||||||
|
proc debug() {.async.} =
|
||||||
|
while true:
|
||||||
|
echo("Sent ", sent)
|
||||||
|
await sleepAsync(1000)
|
||||||
|
|
||||||
|
proc server() {.async.} =
|
||||||
|
var s = newAsyncSocket()
|
||||||
|
s.setSockOpt(OptReuseAddr, true)
|
||||||
|
s.bindAddr(port)
|
||||||
|
s.listen()
|
||||||
|
|
||||||
|
# We're now ready to accept connections, so start the client
|
||||||
|
asyncCheck startClient()
|
||||||
|
asyncCheck debug()
|
||||||
|
|
||||||
|
while true:
|
||||||
|
let client = await accept(s)
|
||||||
|
asyncCheck keepSendingTo(client)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
waitFor server()
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue