diff options
author | Araq <rumpf_a@web.de> | 2014-07-15 09:30:58 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2014-07-15 09:30:58 +0200 |
commit | 0743f78012e954f5295df7923ccabd472a5a7502 (patch) | |
tree | 5d681c9835f01019e8ae83e14c0cd49d1a6c0d38 /lib/pure | |
parent | 7fa399f51c39e6661876223009d5003cd2e0cf99 (diff) | |
parent | 18ded6c23d72cd21fa0aa10ff61dc6f9af40832c (diff) | |
download | Nim-0743f78012e954f5295df7923ccabd472a5a7502.tar.gz |
Merge branch 'master' of https://github.com/Araq/Nimrod
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/algorithm.nim | 9 | ||||
-rw-r--r-- | lib/pure/asyncdispatch.nim | 340 | ||||
-rw-r--r-- | lib/pure/asynchttpserver.nim | 181 | ||||
-rw-r--r-- | lib/pure/asyncnet.nim | 115 | ||||
-rw-r--r-- | lib/pure/collections/sequtils.nim | 63 | ||||
-rw-r--r-- | lib/pure/collections/sets.nim | 49 | ||||
-rw-r--r-- | lib/pure/collections/tables.nim | 344 | ||||
-rw-r--r-- | lib/pure/concurrency/cpuinfo.nim | 58 | ||||
-rw-r--r-- | lib/pure/concurrency/cpuload.nim | 96 | ||||
-rw-r--r-- | lib/pure/concurrency/threadpool.nim | 378 | ||||
-rw-r--r-- | lib/pure/fsmonitor.nim | 6 | ||||
-rw-r--r-- | lib/pure/future.nim | 1 | ||||
-rw-r--r-- | lib/pure/hashes.nim | 29 | ||||
-rw-r--r-- | lib/pure/httpclient.nim | 2 | ||||
-rw-r--r-- | lib/pure/httpserver.nim | 2 | ||||
-rw-r--r-- | lib/pure/json.nim | 85 | ||||
-rw-r--r-- | lib/pure/math.nim | 24 | ||||
-rw-r--r-- | lib/pure/memfiles.nim | 24 | ||||
-rw-r--r-- | lib/pure/net.nim | 75 | ||||
-rw-r--r-- | lib/pure/nimprof.nim | 13 | ||||
-rw-r--r-- | lib/pure/oids.nim | 4 | ||||
-rw-r--r-- | lib/pure/os.nim | 47 | ||||
-rw-r--r-- | lib/pure/osproc.nim | 44 | ||||
-rw-r--r-- | lib/pure/rawsockets.nim | 6 | ||||
-rw-r--r-- | lib/pure/selectors.nim | 82 | ||||
-rw-r--r-- | lib/pure/sockets.nim | 2 | ||||
-rw-r--r-- | lib/pure/strutils.nim | 2 | ||||
-rw-r--r-- | lib/pure/times.nim | 3 |
28 files changed, 1742 insertions, 342 deletions
diff --git a/lib/pure/algorithm.nim b/lib/pure/algorithm.nim index 37fbc948c..86d329763 100644 --- a/lib/pure/algorithm.nim +++ b/lib/pure/algorithm.nim @@ -150,6 +150,15 @@ proc sort*[T](a: var openArray[T], ## # overload: ## sort(myStrArray, system.cmp) ## + ## You can inline adhoc comparison procs with the `do notation + ## <manual.html#do-notation>`_. Example: + ## + ## .. code-block:: nimrod + ## + ## people.sort do (x, y: Person) -> int: + ## result = cmp(x.surname, y.surname) + ## if result == 0: + ## result = cmp(x.name, y.name) var n = a.len var b: seq[T] newSeq(b, n div 2) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index f50383038..d410f8ce1 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -11,8 +11,11 @@ include "system/inclrtl" import os, oids, tables, strutils, macros -import rawsockets -export TPort +import rawsockets, net + +export TPort, TSocketFlags + +#{.injectStmt: newGcInvariant().} ## AsyncDispatch ## ------------- @@ -24,10 +27,12 @@ export TPort ## **Note:** This module is still largely experimental. -# TODO: Discarded void PFutures need to checked for exception. -# TODO: Exceptions are currently uncatchable due to the limitation that -# you cannot have yield in a try stmt. Perhaps I can get the macro to put -# a user's try except around ``future.read``. +# TODO: Discarded void PFutures need to be checked for exception. +# TODO: ``except`` statement (without `try`) does not work. +# TODO: Multiple exception names in a ``except`` don't work. +# TODO: The effect system (raises: []) has trouble with my try transformation. +# TODO: Can't await in a 'except' body +# TODO: getCurrentException(Msg) don't work # -- Futures @@ -35,19 +40,33 @@ type PFutureBase* = ref object of PObject cb: proc () {.closure,gcsafe.} finished: bool + error*: ref EBase + stackTrace: string ## For debugging purposes only. PFuture*[T] = ref object of PFutureBase value: T - error*: ref EBase # TODO: This shouldn't be necessary, generics bug? proc newFuture*[T](): PFuture[T] = ## Creates a new future. new(result) result.finished = false + result.stackTrace = getStackTrace() + +proc checkFinished[T](future: PFuture[T]) = + if future.finished: + echo("<----->") + echo(future.stackTrace) + echo("-----") + when T is string: + echo("Contents: ", future.value.repr) + echo("<----->") + echo("Future already finished, cannot finish twice.") + assert false proc complete*[T](future: PFuture[T], val: T) = ## Completes ``future`` with value ``val``. - assert(not future.finished, "Future already finished, cannot finish twice.") + #assert(not future.finished, "Future already finished, cannot finish twice.") + checkFinished(future) assert(future.error == nil) future.value = val future.finished = true @@ -56,7 +75,8 @@ proc complete*[T](future: PFuture[T], val: T) = proc complete*(future: PFuture[void]) = ## Completes a void ``future``. - assert(not future.finished, "Future already finished, cannot finish twice.") + #assert(not future.finished, "Future already finished, cannot finish twice.") + checkFinished(future) assert(future.error == nil) future.finished = true if future.cb != nil: @@ -64,11 +84,18 @@ proc complete*(future: PFuture[void]) = proc fail*[T](future: PFuture[T], error: ref EBase) = ## Completes ``future`` with ``error``. - assert(not future.finished, "Future already finished, cannot finish twice.") + #assert(not future.finished, "Future already finished, cannot finish twice.") + checkFinished(future) future.finished = true future.error = error if future.cb != nil: future.cb() + else: + # This is to prevent exceptions from being silently ignored when a future + # is discarded. + # TODO: This may turn out to be a bad idea. + # Turns out this is a bad idea. + #raise error proc `callback=`*(future: PFutureBase, cb: proc () {.closure,gcsafe.}) = ## Sets the callback proc to be called when the future completes. @@ -112,10 +139,19 @@ proc finished*[T](future: PFuture[T]): bool = ## ``True`` may indicate an error or a value. Use ``failed`` to distinguish. future.finished -proc failed*[T](future: PFuture[T]): bool = +proc failed*(future: PFutureBase): bool = ## Determines whether ``future`` completed with an error. future.error != nil +proc asyncCheck*[T](future: PFuture[T]) = + ## Sets a callback on ``future`` which raises an exception if the future + ## finished with an error. + ## + ## This should be used instead of ``discard`` to discard void futures. + future.callback = + proc () = + if future.failed: raise future.error + when defined(windows) or defined(nimdoc): import winlean, sets, hashes type @@ -130,15 +166,10 @@ when defined(windows) or defined(nimdoc): ioPort: THandle handles: TSet[TAsyncFD] - TCustomOverlapped = object - Internal*: DWORD - InternalHigh*: DWORD - Offset*: DWORD - OffsetHigh*: DWORD - hEvent*: THANDLE + TCustomOverlapped = object of TOVERLAPPED data*: TCompletionData - PCustomOverlapped = ptr TCustomOverlapped + PCustomOverlapped = ref TCustomOverlapped TAsyncFD* = distinct int @@ -184,27 +215,27 @@ when defined(windows) or defined(nimdoc): else: timeout.int32 var lpNumberOfBytesTransferred: DWORD var lpCompletionKey: ULONG - var lpOverlapped: POverlapped - let res = GetQueuedCompletionStatus(p.ioPort, addr lpNumberOfBytesTransferred, - addr lpCompletionKey, addr lpOverlapped, llTimeout).bool + var customOverlapped: PCustomOverlapped + let res = GetQueuedCompletionStatus(p.ioPort, + addr lpNumberOfBytesTransferred, addr lpCompletionKey, + cast[ptr POverlapped](addr customOverlapped), llTimeout).bool # http://stackoverflow.com/a/12277264/492186 # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html - var customOverlapped = cast[PCustomOverlapped](lpOverlapped) if res: # This is useful for ensuring the reliability of the overlapped struct. assert customOverlapped.data.sock == lpCompletionKey.TAsyncFD customOverlapped.data.cb(customOverlapped.data.sock, lpNumberOfBytesTransferred, TOSErrorCode(-1)) - dealloc(customOverlapped) + GC_unref(customOverlapped) else: let errCode = osLastError() - if lpOverlapped != nil: + if customOverlapped != nil: assert customOverlapped.data.sock == lpCompletionKey.TAsyncFD customOverlapped.data.cb(customOverlapped.data.sock, lpNumberOfBytesTransferred, errCode) - dealloc(customOverlapped) + GC_unref(customOverlapped) else: if errCode.int32 == WAIT_TIMEOUT: # Timed out @@ -300,7 +331,8 @@ when defined(windows) or defined(nimdoc): while it != nil: # "the OVERLAPPED structure must remain valid until the I/O completes" # http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx - var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) + var ol = PCustomOverlapped() + GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) = if not retFuture.finished: @@ -328,7 +360,7 @@ when defined(windows) or defined(nimdoc): success = true break else: - dealloc(ol) + GC_unref(ol) success = false it = it.ai_next @@ -338,7 +370,7 @@ when defined(windows) or defined(nimdoc): return retFuture proc recv*(socket: TAsyncFD, size: int, - flags: int = 0): PFuture[string] = + flags = {TSocketFlags.SafeDisconn}): PFuture[string] = ## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## complete once all the data requested is read, a part of the data has been ## read, or the socket has disconnected in which case the future will @@ -352,15 +384,15 @@ when defined(windows) or defined(nimdoc): # '\0' in the message currently signifies a socket disconnect. Who # knows what will happen when someone sends that to our socket. verifyPresence(socket) - var retFuture = newFuture[string]() - + var retFuture = newFuture[string]() var dataBuf: TWSABuf dataBuf.buf = cast[cstring](alloc0(size)) dataBuf.len = size var bytesReceived: DWord - var flagsio = flags.DWord - var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) + var flagsio = flags.toOSFlags().DWord + var ol = PCustomOverlapped() + GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) = if not retFuture.finished: @@ -374,7 +406,9 @@ when defined(windows) or defined(nimdoc): retFuture.complete($data) else: retFuture.fail(newException(EOS, osErrorMsg(errcode))) - dealloc dataBuf.buf + if dataBuf.buf != nil: + dealloc dataBuf.buf + dataBuf.buf = nil ) let ret = WSARecv(socket.TSocketHandle, addr dataBuf, 1, addr bytesReceived, @@ -382,9 +416,14 @@ when defined(windows) or defined(nimdoc): if ret == -1: let err = osLastError() if err.int32 != ERROR_IO_PENDING: - dealloc dataBuf.buf - dealloc(ol) - retFuture.fail(newException(EOS, osErrorMsg(err))) + if dataBuf.buf != nil: + dealloc dataBuf.buf + dataBuf.buf = nil + GC_unref(ol) + if flags.isDisconnectionError(err): + retFuture.complete("") + else: + retFuture.fail(newException(EOS, osErrorMsg(err))) elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0': # We have to ensure that the buffer is empty because WSARecv will tell # us immediatelly when it was disconnected, even when there is still @@ -415,7 +454,8 @@ when defined(windows) or defined(nimdoc): # free ``ol``. return retFuture - proc send*(socket: TAsyncFD, data: string): PFuture[void] = + proc send*(socket: TAsyncFD, data: string, + flags = {TSocketFlags.SafeDisconn}): PFuture[void] = ## Sends ``data`` to ``socket``. The returned future will complete once all ## data has been sent. verifyPresence(socket) @@ -425,8 +465,9 @@ when defined(windows) or defined(nimdoc): dataBuf.buf = data # since this is not used in a callback, this is fine dataBuf.len = data.len - var bytesReceived, flags: DWord - var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) + var bytesReceived, lowFlags: DWord + var ol = PCustomOverlapped() + GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) = if not retFuture.finished: @@ -437,12 +478,15 @@ when defined(windows) or defined(nimdoc): ) let ret = WSASend(socket.TSocketHandle, addr dataBuf, 1, addr bytesReceived, - flags, cast[POverlapped](ol), nil) + lowFlags, cast[POverlapped](ol), nil) if ret == -1: let err = osLastError() if err.int32 != ERROR_IO_PENDING: - retFuture.fail(newException(EOS, osErrorMsg(err))) - dealloc(ol) + GC_unref(ol) + if flags.isDisconnectionError(err): + retFuture.complete() + else: + retFuture.fail(newException(EOS, osErrorMsg(err))) else: retFuture.complete() # We don't deallocate ``ol`` here because even though this completed @@ -490,7 +534,8 @@ when defined(windows) or defined(nimdoc): client: clientSock.TAsyncFD) ) - var ol = cast[PCustomOverlapped](alloc0(sizeof(TCustomOverlapped))) + var ol = PCustomOverlapped() + GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: proc (sock: TAsyncFD, bytesCount: DWord, errcode: TOSErrorCode) = if not retFuture.finished: @@ -511,7 +556,7 @@ when defined(windows) or defined(nimdoc): let err = osLastError() if err.int32 != ERROR_IO_PENDING: retFuture.fail(newException(EOS, osErrorMsg(err))) - dealloc(ol) + GC_unref(ol) else: completeAccept() # We don't deallocate ``ol`` here because even though this completed @@ -528,15 +573,30 @@ when defined(windows) or defined(nimdoc): result.TSocketHandle.setBlocking(false) register(result) - proc close*(socket: TAsyncFD) = + proc closeSocket*(socket: TAsyncFD) = ## Closes a socket and ensures that it is unregistered. socket.TSocketHandle.close() getGlobalDispatcher().handles.excl(socket) + proc unregister*(fd: TAsyncFD) = + ## Unregisters ``fd``. + getGlobalDispatcher().handles.excl(fd) + initAll() else: import selectors - from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK + when defined(windows): + import winlean + const + EINTR = WSAEINPROGRESS + EINPROGRESS = WSAEINPROGRESS + EWOULDBLOCK = WSAEWOULDBLOCK + EAGAIN = EINPROGRESS + MSG_NOSIGNAL = 0 + else: + from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK, + MSG_NOSIGNAL + type TAsyncFD* = distinct cint TCallback = proc (sock: TAsyncFD): bool {.closure,gcsafe.} @@ -577,11 +637,14 @@ else: result.TSocketHandle.setBlocking(false) register(result) - proc close*(sock: TAsyncFD) = + proc closeSocket*(sock: TAsyncFD) = let disp = getGlobalDispatcher() sock.TSocketHandle.close() disp.selector.unregister(sock.TSocketHandle) + proc unregister*(fd: TAsyncFD) = + getGlobalDispatcher().selector.unregister(fd.TSocketHandle) + proc addRead(sock: TAsyncFD, cb: TCallback) = let p = getGlobalDispatcher() if sock.TSocketHandle notin p.selector: @@ -667,20 +730,23 @@ else: return retFuture proc recv*(socket: TAsyncFD, size: int, - flags: int = 0): PFuture[string] = + flags = {TSocketFlags.SafeDisconn}): PFuture[string] = var retFuture = newFuture[string]() var readBuffer = newString(size) proc cb(sock: TAsyncFD): bool = result = true - let res = recv(sock.TSocketHandle, addr readBuffer[0], size, - flags.cint) + let res = recv(sock.TSocketHandle, addr readBuffer[0], size.cint, + flags.toOSFlags()) #echo("recv cb res: ", res) if res < 0: let lastError = osLastError() - if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: - retFuture.fail(newException(EOS, osErrorMsg(lastError))) + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + if flags.isDisconnectionError(lastError): + retFuture.complete("") + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) else: result = false # We still want this callback to be called. elif res == 0: @@ -689,11 +755,13 @@ else: else: readBuffer.setLen(res) retFuture.complete(readBuffer) - + # TODO: The following causes a massive slowdown. + #if not cb(socket): addRead(socket, cb) return retFuture - proc send*(socket: TAsyncFD, data: string): PFuture[void] = + proc send*(socket: TAsyncFD, data: string, + flags = {TSocketFlags.SafeDisconn}): PFuture[void] = var retFuture = newFuture[void]() var written = 0 @@ -702,11 +770,15 @@ else: result = true let netSize = data.len-written var d = data.cstring - let res = send(sock.TSocketHandle, addr d[written], netSize, 0.cint) + let res = send(sock.TSocketHandle, addr d[written], netSize.cint, + MSG_NOSIGNAL) if res < 0: let lastError = osLastError() if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: - retFuture.fail(newException(EOS, osErrorMsg(lastError))) + if flags.isDisconnectionError(lastError): + retFuture.complete() + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) else: result = false # We still want this callback to be called. else: @@ -715,6 +787,8 @@ else: result = false # We still have data to send. else: retFuture.complete() + # TODO: The following causes crashes. + #if not cb(socket): addWrite(socket, cb) return retFuture @@ -757,41 +831,76 @@ proc accept*(socket: TAsyncFD): PFuture[TAsyncFD] = # -- Await Macro -template createCb*(retFutureSym, iteratorNameSym: expr): stmt {.immediate.} = +template createCb*(retFutureSym, iteratorNameSym, + name: expr): stmt {.immediate.} = var nameIterVar = iteratorNameSym + #{.push stackTrace: off.} proc cb {.closure,gcsafe.} = - if not nameIterVar.finished: - var next = nameIterVar() - if next == nil: - assert retFutureSym.finished, "Async procedure's return Future was not finished." - else: - next.callback = cb + try: + if not nameIterVar.finished: + var next = nameIterVar() + if next == nil: + assert retFutureSym.finished, "Async procedure's (" & + name & ") return Future was not finished." + else: + next.callback = cb + except: + retFutureSym.fail(getCurrentException()) cb() - -template createVar(futSymName: string, asyncProc: PNimrodNode, - valueReceiver: expr) {.immediate, dirty.} = - # TODO: Used template here due to bug #926 + #{.pop.} +proc generateExceptionCheck(futSym, + exceptBranch, rootReceiver: PNimrodNode): PNimrodNode {.compileTime.} = + if exceptBranch == nil: + result = rootReceiver + else: + if exceptBranch[0].kind == nnkStmtList: + result = newIfStmt( + (newDotExpr(futSym, newIdentNode("failed")), + exceptBranch[0] + ) + ) + else: + expectKind(exceptBranch[1], nnkStmtList) + result = newIfStmt( + (newDotExpr(futSym, newIdentNode("failed")), + newIfStmt( + (infix(newDotExpr(futSym, newIdentNode("error")), "of", exceptBranch[0]), + exceptBranch[1]) + ) + ) + ) + let elseNode = newNimNode(nnkElse) + elseNode.add newNimNode(nnkStmtList) + elseNode[0].add rootReceiver + result.add elseNode + +template createVar(result: var PNimrodNode, futSymName: string, + asyncProc: PNimrodNode, + valueReceiver, rootReceiver: expr) = result = newNimNode(nnkStmtList) var futSym = genSym(nskVar, "future") result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y result.add newNimNode(nnkYieldStmt).add(futSym) # -> yield future<x> valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read + result.add generateExceptionCheck(futSym, exceptBranch, rootReceiver) proc processBody(node, retFutureSym: PNimrodNode, - subtypeName: string): PNimrodNode {.compileTime.} = + subTypeIsVoid: bool, + exceptBranch: PNimrodNode): PNimrodNode {.compileTime.} = + #echo(node.treeRepr) result = node case node.kind of nnkReturnStmt: result = newNimNode(nnkStmtList) if node[0].kind == nnkEmpty: - if subtypeName != "void": + if not subtypeIsVoid: result.add newCall(newIdentNode("complete"), retFutureSym, newIdentNode("result")) else: result.add newCall(newIdentNode("complete"), retFutureSym) else: result.add newCall(newIdentNode("complete"), retFutureSym, - node[0].processBody(retFutureSym, subtypeName)) + node[0].processBody(retFutureSym, subtypeIsVoid, exceptBranch)) result.add newNimNode(nnkReturnStmt).add(newNilLit()) return # Don't process the children of this return stmt @@ -804,16 +913,16 @@ proc processBody(node, retFutureSym: PNimrodNode, of nnkCall: # await foo(p, x) var futureValue: PNimrodNode - createVar("future" & $node[1][0].toStrLit, node[1], futureValue) - result.add futureValue + result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue, + futureValue) else: error("Invalid node kind in 'await', got: " & $node[1].kind) elif node[1].kind == nnkCommand and node[1][0].kind == nnkIdent and node[1][0].ident == !"await": # foo await x var newCommand = node - createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1]) - result.add newCommand + result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1], + newCommand) of nnkVarSection, nnkLetSection: case node[0][2].kind @@ -821,9 +930,8 @@ proc processBody(node, retFutureSym: PNimrodNode, if node[0][2][0].ident == !"await": # var x = await y var newVarSection = node # TODO: Should this use copyNimNode? - createVar("future" & $node[0][0].ident, node[0][2][1], - newVarSection[0][2]) - result.add newVarSection + result.createVar("future" & $node[0][0].ident, node[0][2][1], + newVarSection[0][2], newVarSection) else: discard of nnkAsgn: case node[1].kind @@ -831,19 +939,43 @@ proc processBody(node, retFutureSym: PNimrodNode, if node[1][0].ident == !"await": # x = await y var newAsgn = node - createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1]) - result.add newAsgn + result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn) else: discard of nnkDiscardStmt: # discard await x - if node[0][0].kind == nnkIdent and node[0][0].ident == !"await": - var dummy = newNimNode(nnkStmtList) - createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1], dummy) + if node[0].kind != nnkEmpty and node[0][0].kind == nnkIdent and + node[0][0].ident == !"await": + var newDiscard = node + result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1], + newDiscard[0], newDiscard) + of nnkTryStmt: + # try: await x; except: ... + result = newNimNode(nnkStmtList) + proc processForTry(n: PNimrodNode, i: var int, + res: PNimrodNode): bool {.compileTime.} = + result = false + while i < n[0].len: + var processed = processBody(n[0][i], retFutureSym, subtypeIsVoid, n[1]) + if processed.kind != n[0][i].kind or processed.len != n[0][i].len: + expectKind(processed, nnkStmtList) + expectKind(processed[2][1], nnkElse) + i.inc + discard processForTry(n, i, processed[2][1][0]) + res.add processed + result = true + else: + res.add n[0][i] + i.inc + var i = 0 + if not processForTry(node, i, result): + var temp = node + temp[0] = result + result = temp + return else: discard - + for i in 0 .. <result.len: - result[i] = processBody(result[i], retFutureSym, subtypeName) - #echo(treeRepr(result)) + result[i] = processBody(result[i], retFutureSym, subtypeIsVoid, exceptBranch) proc getName(node: PNimrodNode): string {.compileTime.} = case node.kind @@ -851,47 +983,53 @@ proc getName(node: PNimrodNode): string {.compileTime.} = return $node[1].ident of nnkIdent: return $node.ident + of nnkEmpty: + return "anonymous" else: - assert false + error("Unknown name.") macro async*(prc: stmt): stmt {.immediate.} = ## Macro which processes async procedures into the appropriate ## iterators and yield statements. - - expectKind(prc, nnkProcDef) + if prc.kind notin {nnkProcDef, nnkLambda}: + error("Cannot transform this node kind into an async proc." & + " Proc definition or lambda node expected.") hint("Processing " & prc[0].getName & " as an async proc.") let returnType = prc[3][0] - var subtypeName = "" # Verify that the return type is a PFuture[T] if returnType.kind == nnkIdent: error("Expected return type of 'PFuture' got '" & $returnType & "'") elif returnType.kind == nnkBracketExpr: if $returnType[0] != "PFuture": error("Expected return type of 'PFuture' got '" & $returnType[0] & "'") - subtypeName = $returnType[1].ident - elif returnType.kind == nnkEmpty: - subtypeName = "void" + + let subtypeIsVoid = returnType.kind == nnkEmpty or + (returnType.kind == nnkBracketExpr and + returnType[1].kind == nnkIdent and returnType[1].ident == !"void") var outerProcBody = newNimNode(nnkStmtList) # -> var retFuture = newFuture[T]() var retFutureSym = genSym(nskVar, "retFuture") + var subRetType = + if returnType.kind == nnkEmpty: newIdentNode("void") + else: returnType[1] outerProcBody.add( newVarStmt(retFutureSym, newCall( newNimNode(nnkBracketExpr).add( newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`. - newIdentNode(subtypeName))))) # Get type from return type of this proc + subRetType)))) # Get type from return type of this proc # -> iterator nameIter(): PFutureBase {.closure.} = # -> var result: T # -> <proc_body> # -> complete(retFuture, result) var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter") - var procBody = prc[6].processBody(retFutureSym, subtypeName) - if subtypeName != "void": + var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil) + if not subtypeIsVoid: procBody.insert(0, newNimNode(nnkVarSection).add( newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T procBody.add( @@ -908,7 +1046,8 @@ macro async*(prc: stmt): stmt {.immediate.} = # -> createCb(retFuture) var cbName = newIdentNode("cb") - var procCb = newCall("createCb", retFutureSym, iteratorNameSym) + var procCb = newCall("createCb", retFutureSym, iteratorNameSym, + newStrLitNode(prc[0].getName)) outerProcBody.add procCb # -> return retFuture @@ -918,17 +1057,18 @@ macro async*(prc: stmt): stmt {.immediate.} = # Remove the 'async' pragma. for i in 0 .. <result[4].len: - if result[4][i].ident == !"async": + if result[4][i].kind == nnkIdent and result[4][i].ident == !"async": result[4].del(i) - if subtypeName == "void": + if subtypeIsVoid: # Add discardable pragma. - result[4].add(newIdentNode("discardable")) if returnType.kind == nnkEmpty: # Add PFuture[void] result[3][0] = parseExpr("PFuture[void]") result[6] = outerProcBody + #echo(treeRepr(result)) + #if prc[0].getName == "routeReq": #echo(toStrLit(result)) proc recvLine*(socket: TAsyncFD): PFuture[string] {.async.} = @@ -956,7 +1096,7 @@ proc recvLine*(socket: TAsyncFD): PFuture[string] {.async.} = if c.len == 0: return "" if c == "\r": - c = await recv(socket, 1, MSG_PEEK) + c = await recv(socket, 1, {TSocketFlags.SafeDisconn, TSocketFlags.Peek}) if c.len > 0 and c == "\L": discard await recv(socket, 1) addNLIfEmpty() diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 2ebd7036d..ee6658fd1 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -14,12 +14,13 @@ import strtabs, asyncnet, asyncdispatch, parseutils, parseurl, strutils type TRequest* = object - client: PAsyncSocket # TODO: Separate this into a Response object? + client*: PAsyncSocket # TODO: Separate this into a Response object? reqMethod*: string headers*: PStringTable protocol*: tuple[orig: string, major, minor: int] url*: TURL hostname*: string ## The hostname of the client that made the request. + body*: string PAsyncHttpServer* = ref object socket: PAsyncSocket @@ -50,10 +51,15 @@ proc `==`*(protocol: tuple[orig: string, major, minor: int], proc newAsyncHttpServer*(): PAsyncHttpServer = new result -proc sendHeaders*(req: TRequest, headers: PStringTable) {.async.} = - ## Sends the specified headers to the requesting client. +proc addHeaders(msg: var string, headers: PStringTable) = for k, v in headers: - await req.client.send(k & ": " & v & "\c\L") + msg.add(k & ": " & v & "\c\L") + +proc sendHeaders*(req: TRequest, headers: PStringTable): PFuture[void] = + ## Sends the specified headers to the requesting client. + var msg = "" + addHeaders(msg, headers) + return req.client.send(msg) proc respond*(req: TRequest, code: THttpCode, content: string, headers: PStringTable = newStringTable()) {.async.} = @@ -63,9 +69,9 @@ proc respond*(req: TRequest, code: THttpCode, ## This procedure will **not** close the client socket. var customHeaders = headers customHeaders["Content-Length"] = $content.len - await req.client.send("HTTP/1.1 " & $code & "\c\L") - await sendHeaders(req, headers) - await req.client.send("\c\L" & content) + var msg = "HTTP/1.1 " & $code & "\c\L" + msg.addHeaders(customHeaders) + await req.client.send(msg & "\c\L" & content) proc newRequest(): TRequest = result.headers = newStringTable(modeCaseInsensitive) @@ -77,7 +83,7 @@ proc parseHeader(line: string): tuple[key, value: string] = i += line.skipWhiteSpace(i) i += line.parseUntil(result.value, {'\c', '\L'}, i) -proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = +proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = var i = protocol.skipIgnoreCase("HTTP/") if i != 5: raise newException(EInvalidValue, "Invalid request protocol. Got: " & @@ -87,70 +93,95 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = i.inc # Skip . i.inc protocol.parseInt(result.minor, i) +proc sendStatus(client: PAsyncSocket, status: string): PFuture[void] = + client.send("HTTP/1.1 " & status & "\c\L") + proc processClient(client: PAsyncSocket, address: string, callback: proc (request: TRequest): PFuture[void]) {.async.} = - # GET /path HTTP/1.1 - # Header: val - # \n - var request = newRequest() - request.hostname = address - assert client != nil - request.client = client - - # First line - GET /path HTTP/1.1 - let line = await client.recvLine() # TODO: Timeouts. - if line == "": - client.close() - return - let lineParts = line.split(' ') - if lineParts.len != 3: - request.respond(Http400, "Invalid request. Got: " & line) - client.close() - return - - let reqMethod = lineParts[0] - let path = lineParts[1] - let protocol = lineParts[2] - - # Headers - var i = 0 while true: - i = 0 - let headerLine = await client.recvLine() - if headerLine == "": - client.close(); return - if headerLine == "\c\L": break - # TODO: Compiler crash - #let (key, value) = parseHeader(headerLine) - let kv = parseHeader(headerLine) - request.headers[kv.key] = kv.value - - request.reqMethod = reqMethod - request.url = parseUrl(path) - try: - request.protocol = protocol.parseProtocol() - except EInvalidValue: - request.respond(Http400, "Invalid request protocol. Got: " & protocol) - return - - case reqMethod.normalize - of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch": - await callback(request) - else: - request.respond(Http400, "Invalid request method. Got: " & reqMethod) - - # Persistent connections - if (request.protocol == HttpVer11 and - request.headers["connection"].normalize != "close") or - (request.protocol == HttpVer10 and - request.headers["connection"].normalize == "keep-alive"): - # In HTTP 1.1 we assume that connection is persistent. Unless connection - # header states otherwise. - # In HTTP 1.0 we assume that the connection should not be persistent. - # Unless the connection header states otherwise. - await processClient(client, address, callback) - else: - request.client.close() + # GET /path HTTP/1.1 + # Header: val + # \n + var request = newRequest() + request.hostname = address + assert client != nil + request.client = client + + # First line - GET /path HTTP/1.1 + let line = await client.recvLine() # TODO: Timeouts. + if line == "": + client.close() + return + let lineParts = line.split(' ') + if lineParts.len != 3: + await request.respond(Http400, "Invalid request. Got: " & line) + continue + + let reqMethod = lineParts[0] + let path = lineParts[1] + let protocol = lineParts[2] + + # Headers + var i = 0 + while true: + i = 0 + let headerLine = await client.recvLine() + if headerLine == "": + client.close(); return + if headerLine == "\c\L": break + # TODO: Compiler crash + #let (key, value) = parseHeader(headerLine) + let kv = parseHeader(headerLine) + request.headers[kv.key] = kv.value + + request.reqMethod = reqMethod + request.url = parseUrl(path) + try: + request.protocol = protocol.parseProtocol() + except EInvalidValue: + asyncCheck request.respond(Http400, "Invalid request protocol. Got: " & + protocol) + continue + + if reqMethod.normalize == "post": + # Check for Expect header + if request.headers.hasKey("Expect"): + if request.headers["Expect"].toLower == "100-continue": + await client.sendStatus("100 Continue") + else: + await client.sendStatus("417 Expectation Failed") + + # Read the body + # - Check for Content-length header + if request.headers.hasKey("Content-Length"): + var contentLength = 0 + if parseInt(request.headers["Content-Length"], contentLength) == 0: + await request.respond(Http400, "Bad Request. Invalid Content-Length.") + else: + request.body = await client.recv(contentLength) + assert request.body.len == contentLength + else: + await request.respond(Http400, "Bad Request. No Content-Length.") + continue + + case reqMethod.normalize + of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch": + await callback(request) + else: + await request.respond(Http400, "Invalid request method. Got: " & reqMethod) + + # Persistent connections + if (request.protocol == HttpVer11 and + request.headers["connection"].normalize != "close") or + (request.protocol == HttpVer10 and + request.headers["connection"].normalize == "keep-alive"): + # In HTTP 1.1 we assume that connection is persistent. Unless connection + # header states otherwise. + # In HTTP 1.0 we assume that the connection should not be persistent. + # Unless the connection header states otherwise. + else: + request.client.close() + break proc serve*(server: PAsyncHttpServer, port: TPort, callback: proc (request: TRequest): PFuture[void], @@ -167,14 +198,20 @@ proc serve*(server: PAsyncHttpServer, port: TPort, # TODO: Causes compiler crash. #var (address, client) = await server.socket.acceptAddr() var fut = await server.socket.acceptAddr() - processClient(fut.client, fut.address, callback) + asyncCheck processClient(fut.client, fut.address, callback) + +proc close*(server: PAsyncHttpServer) = + ## Terminates the async http server instance. + server.socket.close() when isMainModule: var server = newAsyncHttpServer() proc cb(req: TRequest) {.async.} = #echo(req.reqMethod, " ", req.url) #echo(req.headers) - await req.respond(Http200, "Hello World") + let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT", + "Content-type": "text/plain; charset=utf-8"} + await req.respond(Http200, "Hello World", headers.newStringTable()) - server.serve(TPort(5555), cb) + asyncCheck server.serve(TPort(5555), cb) runForever() diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index b1abf627b..374ac77e3 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -80,7 +80,8 @@ proc connect*(socket: PAsyncSocket, address: string, port: TPort, ## or an error occurs. result = connect(socket.fd.TAsyncFD, address, port, af) -proc readIntoBuf(socket: PAsyncSocket, flags: int): PFuture[int] {.async.} = +proc readIntoBuf(socket: PAsyncSocket, + flags: set[TSocketFlags]): PFuture[int] {.async.} = var data = await recv(socket.fd.TAsyncFD, BufferSize, flags) if data.len != 0: copyMem(addr socket.buffer[0], addr data[0], data.len) @@ -89,7 +90,7 @@ proc readIntoBuf(socket: PAsyncSocket, flags: int): PFuture[int] {.async.} = result = data.len proc recv*(socket: PAsyncSocket, size: int, - flags: int = 0): PFuture[string] {.async.} = + flags = {TSocketFlags.SafeDisconn}): PFuture[string] {.async.} = ## Reads ``size`` bytes from ``socket``. Returned future will complete once ## all of the requested data is read. If socket is disconnected during the ## recv operation then the future may complete with only a part of the @@ -97,37 +98,42 @@ proc recv*(socket: PAsyncSocket, size: int, ## to be read then the future will complete with a value of ``""``. if socket.isBuffered: result = newString(size) - - template returnNow(readBytes: int) = - result.setLen(readBytes) - # Only increase buffer position when not peeking. - if (flags and MSG_PEEK) != MSG_PEEK: - socket.currPos.inc(readBytes) - return + let originalBufPos = socket.currPos if socket.bufLen == 0: - let res = await socket.readIntoBuf(flags and (not MSG_PEEK)) - if res == 0: returnNow(0) + let res = await socket.readIntoBuf(flags - {TSocketFlags.Peek}) + if res == 0: + result.setLen(0) + return var read = 0 while read < size: if socket.currPos >= socket.bufLen: - let res = await socket.readIntoBuf(flags and (not MSG_PEEK)) - if res == 0: returnNow(read) + if TSocketFlags.Peek in flags: + # We don't want to get another buffer if we're peeking. + break + let res = await socket.readIntoBuf(flags - {TSocketFlags.Peek}) + if res == 0: + break let chunk = min(socket.bufLen-socket.currPos, size-read) - copyMem(addr(result[read]), addr(socket.buffer[socket.currPos+read]), chunk) + copyMem(addr(result[read]), addr(socket.buffer[socket.currPos]), chunk) read.inc(chunk) + socket.currPos.inc(chunk) - returnNow(read) + if TSocketFlags.Peek in flags: + # Restore old buffer cursor position. + socket.currPos = originalBufPos + result.setLen(read) else: result = await recv(socket.fd.TAsyncFD, size, flags) -proc send*(socket: PAsyncSocket, data: string): PFuture[void] = +proc send*(socket: PAsyncSocket, data: string, + flags = {TSocketFlags.SafeDisconn}): PFuture[void] = ## Sends ``data`` to ``socket``. The returned future will complete once all ## data has been sent. assert socket != nil - result = send(socket.fd.TAsyncFD, data) + result = send(socket.fd.TAsyncFD, data, flags) proc acceptAddr*(socket: PAsyncSocket): PFuture[tuple[address: string, client: PAsyncSocket]] = @@ -162,7 +168,8 @@ proc accept*(socket: PAsyncSocket): PFuture[PAsyncSocket] = retFut.complete(future.read.client) return retFut -proc recvLine*(socket: PAsyncSocket): PFuture[string] {.async.} = +proc recvLine*(socket: PAsyncSocket, + flags = {TSocketFlags.SafeDisconn}): PFuture[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once ## a full line is read or an error occurs. ## @@ -175,28 +182,60 @@ proc recvLine*(socket: PAsyncSocket): PFuture[string] {.async.} = ## If the socket is disconnected in the middle of a line (before ``\r\L`` ## is read) then line will be set to ``""``. ## The partial line **will be lost**. - + ## + ## **Warning**: The ``Peek`` flag is not yet implemented. template addNLIfEmpty(): stmt = if result.len == 0: result.add("\c\L") + assert TSocketFlags.Peek notin flags ## TODO: + if socket.isBuffered: + result = "" + if socket.bufLen == 0: + let res = await socket.readIntoBuf(flags) + if res == 0: + return - result = "" - var c = "" - while true: - c = await recv(socket, 1) - if c.len == 0: - return "" - if c == "\r": - c = await recv(socket, 1, MSG_PEEK) - if c.len > 0 and c == "\L": - let dummy = await recv(socket, 1) - assert dummy == "\L" - addNLIfEmpty() - return - elif c == "\L": - addNLIfEmpty() - return - add(result.string, c) + var lastR = false + while true: + if socket.currPos >= socket.bufLen: + let res = await socket.readIntoBuf(flags) + if res == 0: + result = "" + break + + case socket.buffer[socket.currPos] + of '\r': + lastR = true + addNLIfEmpty() + of '\L': + addNLIfEmpty() + socket.currPos.inc() + return + else: + if lastR: + socket.currPos.inc() + return + else: + result.add socket.buffer[socket.currPos] + socket.currPos.inc() + else: + result = "" + var c = "" + while true: + c = await recv(socket, 1, flags) + if c.len == 0: + return "" + if c == "\r": + c = await recv(socket, 1, flags + {TSocketFlags.Peek}) + if c.len > 0 and c == "\L": + let dummy = await recv(socket, 1, flags) + assert dummy == "\L" + addNLIfEmpty() + return + elif c == "\L": + addNLIfEmpty() + return + add(result.string, c) proc bindAddr*(socket: PAsyncSocket, port = TPort(0), address = "") = ## Binds ``address``:``port`` to the socket. @@ -214,7 +253,7 @@ proc listen*(socket: PAsyncSocket, backlog = SOMAXCONN) = proc close*(socket: PAsyncSocket) = ## Closes the socket. - socket.fd.TAsyncFD.close() + socket.fd.TAsyncFD.closeSocket() # TODO SSL when isMainModule: @@ -235,7 +274,7 @@ when isMainModule: break else: echo("Got line: ", line) - main() + asyncCheck main() elif test == LowClient: var sock = newAsyncSocket() var f = connect(sock, "irc.freenode.net", TPort(6667)) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index f5db9d3fa..e760c5e02 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -47,19 +47,15 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = result[i] = itm inc(i) -proc distnct*[T](seq1: seq[T]): seq[T] = +proc deduplicate*[T](seq1: seq[T]): seq[T] = ## Returns a new sequence without duplicates. ## - ## This proc is `misspelled` on purpose to avoid a clash with the keyword - ## ``distinct`` used to `define a derived type incompatible with its base - ## type <manual.html#distinct-type>`_. Example: - ## ## .. code-block:: nimrod ## let ## dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] ## dup2 = @["a", "a", "c", "d", "d"] - ## unique1 = distnct(dup1) - ## unique2 = distnct(dup2) + ## unique1 = deduplicate(dup1) + ## unique2 = deduplicate(dup2) ## assert unique1 == @[1, 3, 4, 2, 8] ## assert unique2 == @["a", "c", "d"] result = @[] @@ -182,6 +178,24 @@ proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] = ## assert f2 == @["yellow"] accumulateResult(filter(seq1, pred)) +proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) = + ## Keeps the items in the passed sequence if they fulfilled the predicate. + ## Same as the ``filter`` proc, but modifies the sequence directly. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] + ## filter(floats, proc(x: float): bool = x > 10) + ## assert floats == @[13.0, 12.5, 10.1] + var pos = 0 + for i in 0 .. <len(seq1): + if pred(seq1[i]): + if pos != i: + seq1[pos] = seq1[i] + inc(pos) + setLen(seq1, pos) + proc delete*[T](s: var seq[T], first=0, last=0) = ## Deletes in `s` the items at position `first` .. `last`. This modifies ## `s` itself, it does not return a copy. @@ -252,6 +266,27 @@ template filterIt*(seq1, pred: expr): expr {.immediate.} = if pred: result.add(it) result +template keepItIf*(varSeq, pred: expr) = + ## Convenience template around the ``keepIf`` proc to reduce typing. + ## + ## Unlike the `proc` version, the predicate needs to be an expression using + ## the ``it`` variable for testing, like: ``keepItIf("abcxyz", it == 'x')``. + ## Example: + ## + ## .. code-block:: nimrod + ## var candidates = @["foo", "bar", "baz", "foobar"] + ## keepItIf(candidates, it.len == 3 and it[0] == 'b') + ## assert candidates == @["bar", "baz"] + var pos = 0 + for i in 0 .. <len(varSeq): + let it {.inject.} = varSeq[i] + if pred: + if pos != i: + varSeq[pos] = varSeq[i] + inc(pos) + setLen(varSeq, pos) + + template toSeq*(iter: expr): expr {.immediate.} = ## Transforms any iterator into a sequence. ## @@ -387,8 +422,8 @@ when isMainModule: let dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] dup2 = @["a", "a", "c", "d", "d"] - unique1 = distnct(dup1) - unique2 = distnct(dup2) + unique1 = deduplicate(dup1) + unique2 = deduplicate(dup2) assert unique1 == @[1, 3, 4, 2, 8] assert unique2 == @["a", "c", "d"] @@ -418,6 +453,11 @@ when isMainModule: echo($n) # echoes 4, 8, 4 in separate lines + block: # keepIf test + var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] + keepIf(floats, proc(x: float): bool = x > 10) + assert floats == @[13.0, 12.5, 10.1] + block: # filterIt test let temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] @@ -426,6 +466,11 @@ when isMainModule: assert acceptable == @[-2.0, 24.5, 44.31] assert notAcceptable == @[-272.15, 99.9, -113.44] + block: # keepItIf test + var candidates = @["foo", "bar", "baz", "foobar"] + keepItIf(candidates, it.len == 3 and it[0] == 'b') + assert candidates == @["bar", "baz"] + block: # toSeq test let numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index bc249ed63..4ba67cb2e 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -112,6 +112,10 @@ proc incl*[A](s: var TSet[A], key: A) = ## includes an element `key` in `s`. inclImpl() +proc incl*[A](s: var TSet[A], other: TSet[A]) = + ## includes everything in `other` in `s` + for item in other: incl(s, item) + proc excl*[A](s: var TSet[A], key: A) = ## excludes `key` from the set `s`. var index = rawGet(s, key) @@ -119,6 +123,10 @@ proc excl*[A](s: var TSet[A], key: A) = s.data[index].slot = seDeleted dec(s.counter) +proc excl*[A](s: var TSet[A], other: TSet[A]) = + ## excludes everything in `other` from `s`. + for item in other: excl(s, item) + proc containsOrIncl*[A](s: var TSet[A], key: A): bool = ## returns true if `s` contains `key`, otherwise `key` is included in `s` ## and false is returned. @@ -147,6 +155,43 @@ proc `$`*[A](s: TSet[A]): string = ## The `$` operator for hash sets. dollarImpl() +proc union*[A](s1, s2: TSet[A]): TSet[A] = + ## returns a new set of all items that are contained in at + ## least one of `s1` and `s2` + result = s1 + incl(result, s2) + +proc intersection*[A](s1, s2: TSet[A]): TSet[A] = + ## returns a new set of all items that are contained in both `s1` and `s2` + result = initSet[A](min(s1.data.len, s2.data.len)) + for item in s1: + if item in s2: incl(result, item) + +proc symmetricDifference*[A](s1, s2: TSet[A]): TSet[A] = + ## returns a new set of all items that are contained in either + ## `s1` or `s2`, but not both + result = s1 + for item in s2: + if containsOrIncl(result, item): excl(result, item) + +proc `+`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = + ## alias for `union` + result = union(s1, s2) + +proc `*`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = + ## alias for `intersection` + result = intersection(s1, s2) + +proc `-+-`*[A](s1, s2: TSet[A]): TSet[A] {.inline.} = + ## alias for `symmetricDifference` + result = symmetricDifference(s1, s2) + +proc disjoint*[A](s1, s2: TSet[A]): bool = + ## returns true iff `s1` and `s2` have no items in common + for item in s1: + if item in s2: return false + return true + # ------------------------------ ordered set ------------------------------ type @@ -211,6 +256,10 @@ proc incl*[A](s: var TOrderedSet[A], key: A) = ## includes an element `key` in `s`. inclImpl() +proc incl*[A](s: var TSet[A], other: TOrderedSet[A]) = + ## includes everything in `other` in `s` + for item in other: incl(s, item) + proc containsOrIncl*[A](s: var TOrderedSet[A], key: A): bool = ## returns true if `s` contains `key`, otherwise `key` is included in `s` ## and false is returned. diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index cd28f9af0..ce9df09e1 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -10,6 +10,48 @@ ## The ``tables`` module implements an efficient hash table that is ## a mapping from keys to values. ## +## If you are using simple standard types like ``int`` or ``string`` for the +## keys of the table you won't have any problems, but as soon as you try to use +## a more complex object as a key you will be greeted by a strange compiler +## error:: +## +## Error: type mismatch: got (Person) +## but expected one of: +## hashes.hash(x: openarray[A]): THash +## hashes.hash(x: int): THash +## hashes.hash(x: float): THash +## … +## +## What is happening here is that the types used for table keys require to have +## a ``hash()`` proc which will convert them to a `THash <hashes.html#THash>`_ +## value, and the compiler is listing all the hash functions it knows. After +## you add such a proc for your custom type everything will work. See this +## example: +## +## .. code-block:: nimrod +## type +## Person = object +## firstName, lastName: string +## +## proc hash(x: Person): THash = +## ## Piggyback on the already available string hash proc. +## ## +## ## Without this proc nothing works! +## result = x.firstName.hash !& x.lastName.hash +## result = !$result +## +## var +## salaries = initTable[Person, int]() +## p1, p2: Person +## +## p1.firstName = "Jon" +## p1.lastName = "Ross" +## salaries[p1] = 30_000 +## +## p2.firstName = "소진" +## p2.lastName = "박" +## salaries[p2] = 45_000 +## ## **Note:** The data types declared here have *value semantics*: This means ## that ``=`` performs a copy of the hash table. @@ -25,6 +67,7 @@ type TTable* {.final, myShallow.}[A, B] = object ## generic hash table data: TKeyValuePairSeq[A, B] counter: int + PTable*[A,B] = ref TTable[A, B] when not defined(nimhygiene): {.pragma: dirty.} @@ -103,6 +146,14 @@ proc mget*[A, B](t: var TTable[A, B], key: A): var B = if index >= 0: result = t.data[index].val else: raise newException(EInvalidKey, "key not found: " & $key) +iterator allValues*[A, B](t: TTable[A, B]; key: A): B = + ## iterates over any value in the table `t` that belongs to the given `key`. + var h: THash = hash(key) and high(t.data) + while t.data[h].slot != seEmpty: + if t.data[h].key == key and t.data[h].slot == seFilled: + yield t.data[h].val + h = nextTry(h, high(t.data)) + proc hasKey*[A, B](t: TTable[A, B], key: A): bool = ## returns true iff `key` is in the table `t`. result = rawGet(t, key) >= 0 @@ -190,7 +241,7 @@ proc `$`*[A, B](t: TTable[A, B]): string = ## The `$` operator for hash tables. dollarImpl() -proc `==`*[A, B](s, t: TTable[A, B]): bool = +template equalsImpl() = if s.counter == t.counter: # different insertion orders mean different 'data' seqs, so we have # to use the slow route here: @@ -199,6 +250,9 @@ proc `==`*[A, B](s, t: TTable[A, B]): bool = if t[key] != val: return false return true +proc `==`*[A, B](s, t: TTable[A, B]): bool = + equalsImpl() + proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): TTable[C, B] = ## Index the collection with the proc provided. # TODO: As soon as supported, change collection: A to collection: A[B] @@ -206,6 +260,87 @@ proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): TTable[C, B] = for item in collection: result[index(item)] = item +proc len*[A, B](t: PTable[A, B]): int = + ## returns the number of keys in `t`. + result = t.counter + +iterator pairs*[A, B](t: PTable[A, B]): tuple[key: A, val: B] = + ## iterates over any (key, value) pair in the table `t`. + for h in 0..high(t.data): + if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val) + +iterator mpairs*[A, B](t: PTable[A, B]): tuple[key: A, val: var B] = + ## iterates over any (key, value) pair in the table `t`. The values + ## can be modified. + for h in 0..high(t.data): + if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val) + +iterator keys*[A, B](t: PTable[A, B]): A = + ## iterates over any key in the table `t`. + for h in 0..high(t.data): + if t.data[h].slot == seFilled: yield t.data[h].key + +iterator values*[A, B](t: PTable[A, B]): B = + ## iterates over any value in the table `t`. + for h in 0..high(t.data): + if t.data[h].slot == seFilled: yield t.data[h].val + +iterator mvalues*[A, B](t: PTable[A, B]): var B = + ## iterates over any value in the table `t`. The values can be modified. + for h in 0..high(t.data): + if t.data[h].slot == seFilled: yield t.data[h].val + +proc `[]`*[A, B](t: PTable[A, B], key: A): B = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, + ## default empty value for the type `B` is returned + ## and no exception is raised. One can check with ``hasKey`` whether the key + ## exists. + result = t[][key] + +proc mget*[A, B](t: PTable[A, B], key: A): var B = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. + t[].mget(key) + +proc hasKey*[A, B](t: PTable[A, B], key: A): bool = + ## returns true iff `key` is in the table `t`. + result = t[].hasKey(key) + +proc `[]=`*[A, B](t: PTable[A, B], key: A, val: B) = + ## puts a (key, value)-pair into `t`. + t[][key] = val + +proc add*[A, B](t: PTable[A, B], key: A, val: B) = + ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + t[].add(key, val) + +proc del*[A, B](t: PTable[A, B], key: A) = + ## deletes `key` from hash table `t`. + t[].del(key) + +proc newTable*[A, B](initialSize=64): PTable[A, B] = + new(result) + result[] = initTable[A, B](initialSize) + +proc newTable*[A, B](pairs: openArray[tuple[key: A, val: B]]): PTable[A, B] = + ## creates a new hash table that contains the given `pairs`. + new(result) + result[] = toTable[A, B](pairs) + +proc `$`*[A, B](t: PTable[A, B]): string = + ## The `$` operator for hash tables. + dollarImpl() + +proc `==`*[A, B](s, t: PTable[A, B]): bool = + equalsImpl() + +proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): PTable[C, B] = + ## Index the collection with the proc provided. + # TODO: As soon as supported, change collection: A to collection: A[B] + result = newTable[C, B]() + for item in collection: + result[index(item)] = item + # ------------------------------ ordered table ------------------------------ type @@ -216,6 +351,7 @@ type final, myShallow.}[A, B] = object ## table that remembers insertion order data: TOrderedKeyValuePairSeq[A, B] counter, first, last: int + POrderedTable*[A, B] = ref TOrderedTable[A, B] proc len*[A, B](t: TOrderedTable[A, B]): int {.inline.} = ## returns the number of keys in `t`. @@ -376,6 +512,96 @@ proc sort*[A, B](t: var TOrderedTable[A, B], t.first = list t.last = tail +proc len*[A, B](t: POrderedTable[A, B]): int {.inline.} = + ## returns the number of keys in `t`. + result = t.counter + +template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = + var h = t.first + while h >= 0: + var nxt = t.data[h].next + if t.data[h].slot == seFilled: yieldStmt + h = nxt + +iterator pairs*[A, B](t: POrderedTable[A, B]): tuple[key: A, val: B] = + ## iterates over any (key, value) pair in the table `t` in insertion + ## order. + forAllOrderedPairs: + yield (t.data[h].key, t.data[h].val) + +iterator mpairs*[A, B](t: POrderedTable[A, B]): tuple[key: A, val: var B] = + ## iterates over any (key, value) pair in the table `t` in insertion + ## order. The values can be modified. + forAllOrderedPairs: + yield (t.data[h].key, t.data[h].val) + +iterator keys*[A, B](t: POrderedTable[A, B]): A = + ## iterates over any key in the table `t` in insertion order. + forAllOrderedPairs: + yield t.data[h].key + +iterator values*[A, B](t: POrderedTable[A, B]): B = + ## iterates over any value in the table `t` in insertion order. + forAllOrderedPairs: + yield t.data[h].val + +iterator mvalues*[A, B](t: POrderedTable[A, B]): var B = + ## iterates over any value in the table `t` in insertion order. The values + ## can be modified. + forAllOrderedPairs: + yield t.data[h].val + +proc `[]`*[A, B](t: POrderedTable[A, B], key: A): B = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, + ## default empty value for the type `B` is returned + ## and no exception is raised. One can check with ``hasKey`` whether the key + ## exists. + result = t[][key] + +proc mget*[A, B](t: POrderedTable[A, B], key: A): var B = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. + result = t[].mget(key) + +proc hasKey*[A, B](t: POrderedTable[A, B], key: A): bool = + ## returns true iff `key` is in the table `t`. + result = t[].hasKey(key) + +proc `[]=`*[A, B](t: POrderedTable[A, B], key: A, val: B) = + ## puts a (key, value)-pair into `t`. + t[][key] = val + +proc add*[A, B](t: POrderedTable[A, B], key: A, val: B) = + ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + t[].add(key, val) + +proc newOrderedTable*[A, B](initialSize=64): POrderedTable[A, B] = + ## creates a new ordered hash table that is empty. + ## + ## `initialSize` needs to be a power of two. If you need to accept runtime + ## values for this you could use the ``nextPowerOfTwo`` proc from the + ## `math <math.html>`_ module. + new(result) + result[] = initOrderedTable[A, B]() + +proc newOrderedTable*[A, B](pairs: openArray[tuple[key: A, + val: B]]): POrderedTable[A, B] = + ## creates a new ordered hash table that contains the given `pairs`. + result = newOrderedTable[A, B](nextPowerOfTwo(pairs.len+10)) + for key, val in items(pairs): result[key] = val + +proc `$`*[A, B](t: POrderedTable[A, B]): string = + ## The `$` operator for ordered hash tables. + dollarImpl() + +proc sort*[A, B](t: POrderedTable[A, B], + cmp: proc (x,y: tuple[key: A, val: B]): int) = + ## sorts `t` according to `cmp`. This modifies the internal list + ## that kept the insertion order, so insertion order is lost after this + ## call but key lookup and insertions remain possible after `sort` (in + ## contrast to the `sort` for count tables). + t[].sort(cmp) + # ------------------------------ count tables ------------------------------- type @@ -383,6 +609,7 @@ type A] = object ## table that counts the number of each key data: seq[tuple[key: A, val: int]] counter: int + PCountTable*[A] = ref TCountTable[A] proc len*[A](t: TCountTable[A]): int = ## returns the number of keys in `t`. @@ -526,3 +753,118 @@ proc sort*[A](t: var TCountTable[A]) = if j < h: break if h == 1: break +proc len*[A](t: PCountTable[A]): int = + ## returns the number of keys in `t`. + result = t.counter + +iterator pairs*[A](t: PCountTable[A]): tuple[key: A, val: int] = + ## iterates over any (key, value) pair in the table `t`. + for h in 0..high(t.data): + if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) + +iterator mpairs*[A](t: PCountTable[A]): tuple[key: A, val: var int] = + ## iterates over any (key, value) pair in the table `t`. The values can + ## be modified. + for h in 0..high(t.data): + if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) + +iterator keys*[A](t: PCountTable[A]): A = + ## iterates over any key in the table `t`. + for h in 0..high(t.data): + if t.data[h].val != 0: yield t.data[h].key + +iterator values*[A](t: PCountTable[A]): int = + ## iterates over any value in the table `t`. + for h in 0..high(t.data): + if t.data[h].val != 0: yield t.data[h].val + +iterator mvalues*[A](t: PCountTable[A]): var int = + ## iterates over any value in the table `t`. The values can be modified. + for h in 0..high(t.data): + if t.data[h].val != 0: yield t.data[h].val + +proc `[]`*[A](t: PCountTable[A], key: A): int = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, + ## 0 is returned. One can check with ``hasKey`` whether the key + ## exists. + result = t[][key] + +proc mget*[A](t: PCountTable[A], key: A): var int = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. + result = t[].mget(key) + +proc hasKey*[A](t: PCountTable[A], key: A): bool = + ## returns true iff `key` is in the table `t`. + result = t[].hasKey(key) + +proc `[]=`*[A](t: PCountTable[A], key: A, val: int) = + ## puts a (key, value)-pair into `t`. `val` has to be positive. + assert val > 0 + t[][key] = val + +proc newCountTable*[A](initialSize=64): PCountTable[A] = + ## creates a new count table that is empty. + ## + ## `initialSize` needs to be a power of two. If you need to accept runtime + ## values for this you could use the ``nextPowerOfTwo`` proc from the + ## `math <math.html>`_ module. + new(result) + result[] = initCountTable[A](initialSize) + +proc newCountTable*[A](keys: openArray[A]): PCountTable[A] = + ## creates a new count table with every key in `keys` having a count of 1. + result = newCountTable[A](nextPowerOfTwo(keys.len+10)) + for key in items(keys): result[key] = 1 + +proc `$`*[A](t: PCountTable[A]): string = + ## The `$` operator for count tables. + dollarImpl() + +proc inc*[A](t: PCountTable[A], key: A, val = 1) = + ## increments `t[key]` by `val`. + t[].inc(key, val) + +proc smallest*[A](t: PCountTable[A]): tuple[key: A, val: int] = + ## returns the largest (key,val)-pair. Efficiency: O(n) + t[].smallest + +proc largest*[A](t: PCountTable[A]): tuple[key: A, val: int] = + ## returns the (key,val)-pair with the largest `val`. Efficiency: O(n) + t[].largest + +proc sort*[A](t: PCountTable[A]) = + ## sorts the count table so that the entry with the highest counter comes + ## first. This is destructive! You must not modify `t` afterwards! + ## You can use the iterators `pairs`, `keys`, and `values` to iterate over + ## `t` in the sorted order. + t[].sort + +when isMainModule: + type + Person = object + firstName, lastName: string + + proc hash(x: Person): THash = + ## Piggyback on the already available string hash proc. + ## + ## Without this proc nothing works! + result = x.firstName.hash !& x.lastName.hash + result = !$result + + var + salaries = initTable[Person, int]() + p1, p2: Person + p1.firstName = "Jon" + p1.lastName = "Ross" + salaries[p1] = 30_000 + p2.firstName = "소진" + p2.lastName = "박" + salaries[p2] = 45_000 + var + s2 = initOrderedTable[Person, int]() + s3 = initCountTable[Person]() + s2[p1] = 30_000 + s2[p2] = 45_000 + s3[p1] = 30_000 + s3[p2] = 45_000 diff --git a/lib/pure/concurrency/cpuinfo.nim b/lib/pure/concurrency/cpuinfo.nim new file mode 100644 index 000000000..dfa819f64 --- /dev/null +++ b/lib/pure/concurrency/cpuinfo.nim @@ -0,0 +1,58 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements procs to determine the number of CPUs / cores. + +include "system/inclrtl" + +import strutils, os + +when not defined(windows): + import posix + +when defined(linux): + import linux + +when defined(macosx) or defined(bsd): + const + CTL_HW = 6 + HW_AVAILCPU = 25 + HW_NCPU = 3 + proc sysctl(x: ptr array[0..3, cint], y: cint, z: pointer, + a: var csize, b: pointer, c: int): cint {. + importc: "sysctl", header: "<sys/sysctl.h>".} + +proc countProcessors*(): int {.rtl, extern: "ncpi$1".} = + ## returns the numer of the processors/cores the machine has. + ## Returns 0 if it cannot be detected. + when defined(windows): + var x = getEnv("NUMBER_OF_PROCESSORS") + if x.len > 0: result = parseInt(x.string) + elif defined(macosx) or defined(bsd): + var + mib: array[0..3, cint] + numCPU: int + len: csize + mib[0] = CTL_HW + mib[1] = HW_AVAILCPU + len = sizeof(numCPU) + discard sysctl(addr(mib), 2, addr(numCPU), len, nil, 0) + if numCPU < 1: + mib[1] = HW_NCPU + discard sysctl(addr(mib), 2, addr(numCPU), len, nil, 0) + result = numCPU + elif defined(hpux): + result = mpctl(MPC_GETNUMSPUS, nil, nil) + elif defined(irix): + var SC_NPROC_ONLN {.importc: "_SC_NPROC_ONLN", header: "<unistd.h>".}: cint + result = sysconf(SC_NPROC_ONLN) + else: + result = sysconf(SC_NPROCESSORS_ONLN) + if result <= 0: result = 1 + diff --git a/lib/pure/concurrency/cpuload.nim b/lib/pure/concurrency/cpuload.nim new file mode 100644 index 000000000..3cf6a7392 --- /dev/null +++ b/lib/pure/concurrency/cpuload.nim @@ -0,0 +1,96 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements a helper for a thread pool to determine whether +## creating a thread is a good idea. + +when defined(windows): + import winlean, os, strutils, math + + proc `-`(a, b: TFILETIME): int64 = a.rdFileTime - b.rdFileTime +elif defined(linux): + from cpuinfo import countProcessors + +type + ThreadPoolAdvice* = enum + doNothing, + doCreateThread, # create additional thread for throughput + doShutdownThread # too many threads are busy, shutdown one + + ThreadPoolState* = object + when defined(windows): + prevSysKernel, prevSysUser, prevProcKernel, prevProcUser: TFILETIME + calls*: int + +proc advice*(s: var ThreadPoolState): ThreadPoolAdvice = + when defined(windows): + var + sysIdle, sysKernel, sysUser, + procCreation, procExit, procKernel, procUser: TFILETIME + if getSystemTimes(sysIdle, sysKernel, sysUser) == 0 or + getProcessTimes(THandle(-1), procCreation, procExit, + procKernel, procUser) == 0: + return doNothing + if s.calls > 0: + let + sysKernelDiff = sysKernel - s.prevSysKernel + sysUserDiff = sysUser - s.prevSysUser + + procKernelDiff = procKernel - s.prevProcKernel + procUserDiff = procUser - s.prevProcUser + + sysTotal = int(sysKernelDiff + sysUserDiff) + procTotal = int(procKernelDiff + procUserDiff) + # total CPU usage < 85% --> create a new worker thread. + # Measurements show that 100% and often even 90% is not reached even + # if all my cores are busy. + if sysTotal == 0 or procTotal / sysTotal < 0.85: + result = doCreateThread + s.prevSysKernel = sysKernel + s.prevSysUser = sysUser + s.prevProcKernel = procKernel + s.prevProcUser = procUser + elif defined(linux): + proc fscanf(c: TFile, frmt: cstring) {.varargs, importc, + header: "<stdio.h>".} + + var f = open("/proc/loadavg") + var b: float + var busy, total: int + fscanf(f,"%lf %lf %lf %ld/%ld", + addr b, addr b, addr b, addr busy, addr total) + f.close() + let cpus = countProcessors() + if busy-1 < cpus: + result = doCreateThread + elif busy-1 >= cpus*2: + result = doShutdownThread + else: + result = doNothing + else: + # XXX implement this for other OSes + result = doNothing + inc s.calls + +when isMainModule: + proc busyLoop() = + while true: + discard random(80) + os.sleep(100) + + spawn busyLoop() + spawn busyLoop() + spawn busyLoop() + spawn busyLoop() + + var s: ThreadPoolState + + for i in 1 .. 70: + echo advice(s) + os.sleep(1000) diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim new file mode 100644 index 000000000..fd1041918 --- /dev/null +++ b/lib/pure/concurrency/threadpool.nim @@ -0,0 +1,378 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Implements Nimrod's 'spawn'. + +import cpuinfo, cpuload, locks + +{.push stackTrace:off.} + +type + CondVar = object + c: TCond + L: TLock + counter: int + +proc createCondVar(): CondVar = + initCond(result.c) + initLock(result.L) + +proc destroyCondVar(cv: var CondVar) {.inline.} = + deinitCond(cv.c) + deinitLock(cv.L) + +proc await(cv: var CondVar) = + acquire(cv.L) + while cv.counter <= 0: + wait(cv.c, cv.L) + dec cv.counter + release(cv.L) + +proc signal(cv: var CondVar) = + acquire(cv.L) + inc cv.counter + release(cv.L) + signal(cv.c) + +const CacheLineSize = 32 # true for most archs + +type + Barrier {.compilerProc.} = object + entered: int + cv: CondVar # condvar takes 3 words at least + when sizeof(int) < 8: + cacheAlign: array[CacheLineSize-4*sizeof(int), byte] + left: int + cacheAlign2: array[CacheLineSize-sizeof(int), byte] + interest: bool ## wether the master is interested in the "all done" event + +proc barrierEnter(b: ptr Barrier) {.compilerProc, inline.} = + # due to the signaling between threads, it is ensured we are the only + # one with access to 'entered' so we don't need 'atomicInc' here: + inc b.entered + # also we need no 'fence' instructions here as soon 'nimArgsPassingDone' + # will be called which already will perform a fence for us. + +proc barrierLeave(b: ptr Barrier) {.compilerProc, inline.} = + atomicInc b.left + when not defined(x86): fence() + if b.interest and b.left == b.entered: signal(b.cv) + +proc openBarrier(b: ptr Barrier) {.compilerProc, inline.} = + b.entered = 0 + b.left = 0 + b.interest = false + +proc closeBarrier(b: ptr Barrier) {.compilerProc.} = + fence() + if b.left != b.entered: + b.cv = createCondVar() + fence() + b.interest = true + fence() + while b.left != b.entered: await(b.cv) + destroyCondVar(b.cv) + +{.pop.} + +# ---------------------------------------------------------------------------- + +type + foreign* = object ## a region that indicates the pointer comes from a + ## foreign thread heap. + AwaitInfo = object + cv: CondVar + idx: int + + FlowVarBase* = ref FlowVarBaseObj ## untyped base class for 'FlowVar[T]' + FlowVarBaseObj = object of TObject + ready, usesCondVar: bool + cv: CondVar #\ + # for 'awaitAny' support + ai: ptr AwaitInfo + idx: int + data: pointer # we incRef and unref it to keep it alive + owner: pointer # ptr Worker + + FlowVarObj[T] = object of FlowVarBaseObj + blob: T + + FlowVar*{.compilerProc.}[T] = ref FlowVarObj[T] ## a data flow variable + + ToFreeQueue = object + len: int + lock: TLock + empty: TCond + data: array[512, pointer] + + WorkerProc = proc (thread, args: pointer) {.nimcall, gcsafe.} + Worker = object + taskArrived: CondVar + taskStarted: CondVar #\ + # task data: + f: WorkerProc + data: pointer + ready: bool # put it here for correct alignment! + initialized: bool # whether it has even been initialized + shutdown: bool # the pool requests to shut down this worker thread + q: ToFreeQueue + +proc await*(fv: FlowVarBase) = + ## waits until the value for the flowVar arrives. Usually it is not necessary + ## to call this explicitly. + if fv.usesCondVar: + fv.usesCondVar = false + await(fv.cv) + destroyCondVar(fv.cv) + +proc finished(fv: FlowVarBase) = + doAssert fv.ai.isNil, "flowVar is still attached to an 'awaitAny'" + # we have to protect against the rare cases where the owner of the flowVar + # simply disregards the flowVar and yet the "flowVarr" has not yet written + # anything to it: + await(fv) + if fv.data.isNil: return + let owner = cast[ptr Worker](fv.owner) + let q = addr(owner.q) + var waited = false + while true: + acquire(q.lock) + if q.len < q.data.len: + q.data[q.len] = fv.data + inc q.len + release(q.lock) + break + else: + # the queue is exhausted! We block until it has been cleaned: + release(q.lock) + wait(q.empty, q.lock) + waited = true + fv.data = nil + # wakeup other potentially waiting threads: + if waited: signal(q.empty) + +proc cleanFlowVars(w: ptr Worker) = + let q = addr(w.q) + acquire(q.lock) + for i in 0 .. <q.len: + GC_unref(cast[PObject](q.data[i])) + q.len = 0 + release(q.lock) + signal(q.empty) + +proc fvFinalizer[T](fv: FlowVar[T]) = finished(fv) + +proc nimCreateFlowVar[T](): FlowVar[T] {.compilerProc.} = + new(result, fvFinalizer) + +proc nimFlowVarCreateCondVar(fv: FlowVarBase) {.compilerProc.} = + fv.cv = createCondVar() + fv.usesCondVar = true + +proc nimFlowVarSignal(fv: FlowVarBase) {.compilerProc.} = + if fv.ai != nil: + acquire(fv.ai.cv.L) + fv.ai.idx = fv.idx + inc fv.ai.cv.counter + release(fv.ai.cv.L) + signal(fv.ai.cv.c) + if fv.usesCondVar: signal(fv.cv) + +proc awaitAndThen*[T](fv: FlowVar[T]; action: proc (x: T) {.closure.}) = + ## blocks until the ``fv`` is available and then passes its value + ## to ``action``. Note that due to Nimrod's parameter passing semantics this + ## means that ``T`` doesn't need to be copied and so ``awaitAndThen`` can + ## sometimes be more efficient than ``^``. + await(fv) + when T is string or T is seq: + action(cast[T](fv.data)) + elif T is ref: + {.error: "'awaitAndThen' not available for FlowVar[ref]".} + else: + action(fv.blob) + finished(fv) + +proc `^`*[T](fv: FlowVar[ref T]): foreign ptr T = + ## blocks until the value is available and then returns this value. + await(fv) + result = cast[foreign ptr T](fv.data) + +proc `^`*[T](fv: FlowVar[T]): T = + ## blocks until the value is available and then returns this value. + await(fv) + when T is string or T is seq: + result = cast[T](fv.data) + else: + result = fv.blob + +proc awaitAny*(flowVars: openArray[FlowVarBase]): int = + ## awaits any of the given flowVars. Returns the index of one flowVar for + ## which a value arrived. A flowVar only supports one call to 'awaitAny' at + ## the same time. That means if you await([a,b]) and await([b,c]) the second + ## call will only await 'c'. If there is no flowVar left to be able to wait + ## on, -1 is returned. + ## **Note**: This results in non-deterministic behaviour and so should be + ## avoided. + var ai: AwaitInfo + ai.cv = createCondVar() + var conflicts = 0 + for i in 0 .. flowVars.high: + if cas(addr flowVars[i].ai, nil, addr ai): + flowVars[i].idx = i + else: + inc conflicts + if conflicts < flowVars.len: + await(ai.cv) + result = ai.idx + for i in 0 .. flowVars.high: + discard cas(addr flowVars[i].ai, addr ai, nil) + else: + result = -1 + destroyCondVar(ai.cv) + +proc nimArgsPassingDone(p: pointer) {.compilerProc.} = + let w = cast[ptr Worker](p) + signal(w.taskStarted) + +const + MaxThreadPoolSize* = 256 ## maximal size of the thread pool. 256 threads + ## should be good enough for anybody ;-) + +var + currentPoolSize: int + maxPoolSize = MaxThreadPoolSize + minPoolSize = 4 + gSomeReady = createCondVar() + readyWorker: ptr Worker + +proc slave(w: ptr Worker) {.thread.} = + while true: + w.ready = true + readyWorker = w + signal(gSomeReady) + await(w.taskArrived) + assert(not w.ready) + w.f(w, w.data) + if w.q.len != 0: w.cleanFlowVars + if w.shutdown: + w.shutdown = false + atomicDec currentPoolSize + +proc setMinPoolSize*(size: range[1..MaxThreadPoolSize]) = + ## sets the minimal thread pool size. The default value of this is 4. + minPoolSize = size + +proc setMaxPoolSize*(size: range[1..MaxThreadPoolSize]) = + ## sets the minimal thread pool size. The default value of this + ## is ``MaxThreadPoolSize``. + maxPoolSize = size + +var + workers: array[MaxThreadPoolSize, TThread[ptr Worker]] + workersData: array[MaxThreadPoolSize, Worker] + +proc activateThread(i: int) {.noinline.} = + workersData[i].taskArrived = createCondVar() + workersData[i].taskStarted = createCondVar() + workersData[i].initialized = true + initCond(workersData[i].q.empty) + initLock(workersData[i].q.lock) + createThread(workers[i], slave, addr(workersData[i])) + +proc setup() = + currentPoolSize = min(countProcessors(), MaxThreadPoolSize) + readyWorker = addr(workersData[0]) + for i in 0.. <currentPoolSize: activateThread(i) + +proc preferSpawn*(): bool = + ## Use this proc to determine quickly if a 'spawn' or a direct call is + ## preferable. If it returns 'true' a 'spawn' may make sense. In general + ## it is not necessary to call this directly; use 'spawnX' instead. + result = gSomeReady.counter > 0 + +proc spawn*(call: expr): expr {.magic: "Spawn".} + ## always spawns a new task, so that the 'call' is never executed on + ## the calling thread. 'call' has to be proc call 'p(...)' where 'p' + ## is gcsafe and has a return type that is either 'void' or compatible + ## with ``FlowVar[T]``. + +template spawnX*(call: expr): expr = + ## spawns a new task if a CPU core is ready, otherwise executes the + ## call in the calling thread. Usually it is advised to + ## use 'spawn' in order to not block the producer for an unknown + ## amount of time. 'call' has to be proc call 'p(...)' where 'p' + ## is gcsafe and has a return type that is either 'void' or compatible + ## with ``FlowVar[T]``. + (if preferSpawn(): spawn call else: call) + +proc parallel*(body: stmt) {.magic: "Parallel".} + ## a parallel section can be used to execute a block in parallel. ``body`` + ## has to be in a DSL that is a particular subset of the language. Please + ## refer to the manual for further information. + +var + state: ThreadPoolState + stateLock: TLock + +initLock stateLock + +proc selectWorker(w: ptr Worker; fn: WorkerProc; data: pointer): bool = + if cas(addr w.ready, true, false): + w.data = data + w.f = fn + signal(w.taskArrived) + await(w.taskStarted) + result = true + +proc nimSpawn(fn: WorkerProc; data: pointer) {.compilerProc.} = + # implementation of 'spawn' that is used by the code generator. + while true: + if selectWorker(readyWorker, fn, data): return + for i in 0.. <currentPoolSize: + if selectWorker(addr(workersData[i]), fn, data): return + # determine what to do, but keep in mind this is expensive too: + # state.calls < maxPoolSize: warmup phase + # (state.calls and 127) == 0: periodic check + if state.calls < maxPoolSize or (state.calls and 127) == 0: + # ensure the call to 'advice' is atomic: + if tryAcquire(stateLock): + case advice(state) + of doNothing: discard + of doCreateThread: + if currentPoolSize < maxPoolSize: + if not workersData[currentPoolSize].initialized: + activateThread(currentPoolSize) + let w = addr(workersData[currentPoolSize]) + atomicInc currentPoolSize + if selectWorker(w, fn, data): + release(stateLock) + return + # else we didn't succeed but some other thread, so do nothing. + of doShutdownThread: + if currentPoolSize > minPoolSize: + let w = addr(workersData[currentPoolSize-1]) + w.shutdown = true + # we don't free anything here. Too dangerous. + release(stateLock) + # else the acquire failed, but this means some + # other thread succeeded, so we don't need to do anything here. + await(gSomeReady) + +proc sync*() = + ## a simple barrier to wait for all spawn'ed tasks. If you need more elaborate + ## waiting, you have to use an explicit barrier. + while true: + var allReady = true + for i in 0 .. <currentPoolSize: + if not allReady: break + allReady = allReady and workersData[i].ready + if allReady: break + await(gSomeReady) + +setup() diff --git a/lib/pure/fsmonitor.nim b/lib/pure/fsmonitor.nim index d6584c1a0..b35466771 100644 --- a/lib/pure/fsmonitor.nim +++ b/lib/pure/fsmonitor.nim @@ -119,8 +119,8 @@ proc getEvent(m: PFSMonitor, fd: cint): seq[TMonitorEvent] = var mev: TMonitorEvent mev.wd = event.wd if event.len.int != 0: - mev.name = newString(event.len.int) - copyMem(addr(mev.name[0]), addr event.name, event.len.int-1) + let cstr = event.name.addr.cstring + mev.name = $cstr else: mev.name = "" @@ -211,4 +211,4 @@ when isMainModule: while true: if not disp.poll(): break - \ No newline at end of file + diff --git a/lib/pure/future.nim b/lib/pure/future.nim index e0e4c4176..b7df05207 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -18,7 +18,6 @@ proc createProcType(p, b: PNimrodNode): PNimrodNode {.compileTime.} = result = newNimNode(nnkProcTy) var formalParams = newNimNode(nnkFormalParams) - expectKind(b, nnkIdent) formalParams.add b case p.kind diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim index 5784a96c1..740355e55 100644 --- a/lib/pure/hashes.nim +++ b/lib/pure/hashes.nim @@ -8,7 +8,34 @@ # ## This module implements efficient computations of hash values for diverse -## Nimrod types. +## Nimrod types. All the procs are based on these two building blocks: the `!& +## proc <#!&>`_ used to start or mix a hash value, and the `!$ proc <#!$>`_ +## used to *finish* the hash value. If you want to implement hash procs for +## your custom types you will end up writing the following kind of skeleton of +## code: +## +## .. code-block:: nimrod +## proc hash(x: Something): THash = +## ## Computes a THash from `x`. +## var h: THash = 0 +## # Iterate over parts of `x`. +## for xAtom in x: +## # Mix the atom with the partial hash. +## h = h !& xAtom +## # Finish the hash. +## result = !$h +## +## If your custom types contain fields for which there already is a hash proc, +## like for example objects made up of ``strings``, you can simply hash +## together the hash value of the individual fields: +## +## .. code-block:: nimrod +## proc hash(x: Something): THash = +## ## Computes a THash from `x`. +## var h: THash = 0 +## h = h &! hash(x.foo) +## h = h &! hash(x.bar) +## result = !$h import strutils diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index be06a7b8e..9bacc80d6 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -654,7 +654,7 @@ when isMainModule: resp = await client.request("http://nimrod-lang.org/download.html") echo("Got response: ", resp.status) - main() + asyncCheck main() runForever() else: diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 8de708c5d..885742b64 100644 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -192,7 +192,7 @@ when false: if path[path.len-1] == '/' or existsDir(path): path = path / "index.html" - if not ExistsFile(path): + if not existsFile(path): discardHeaders(client) notFound(client) else: diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 4250847e5..508e564c5 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -619,6 +619,44 @@ proc `%`*(elements: openArray[PJsonNode]): PJsonNode = newSeq(result.elems, elements.len) for i, p in pairs(elements): result.elems[i] = p +proc `==`* (a,b: PJsonNode): bool = + ## Check two nodes for equality + if a.kind != b.kind: false + else: + case a.kind + of JString: + a.str == b.str + of JInt: + a.num == b.num + of JFloat: + a.fnum == b.fnum + of JBool: + a.bval == b.bval + of JNull: + true + of JArray: + a.elems == b.elems + of JObject: + a.fields == b.fields + +proc hash* (n:PJsonNode): THash = + ## Compute the hash for a JSON node + case n.kind + of JArray: + result = hash(n.elems) + of JObject: + result = hash(n.fields) + of JInt: + result = hash(n.num) + of JFloat: + result = hash(n.fnum) + of JBool: + result = hash(n.bval.int) + of JString: + result = hash(n.str) + of JNull: + result = hash(0) + proc len*(n: PJsonNode): int = ## If `n` is a `JArray`, it returns the number of elements. ## If `n` is a `JObject`, it returns the number of pairs. @@ -629,7 +667,9 @@ proc len*(n: PJsonNode): int = else: discard proc `[]`*(node: PJsonNode, name: string): PJsonNode = - ## Gets a field from a `JObject`. Returns nil if the key is not found. + ## Gets a field from a `JObject`, which must not be nil. + ## If the value at `name` does not exist, returns nil + assert(not isNil(node)) assert(node.kind == JObject) for key, item in items(node.fields): if key == name: @@ -637,7 +677,9 @@ proc `[]`*(node: PJsonNode, name: string): PJsonNode = return nil proc `[]`*(node: PJsonNode, index: int): PJsonNode = - ## Gets the node at `index` in an Array. + ## Gets the node at `index` in an Array. Result is undefined if `index` + ## is out of bounds + assert(not isNil(node)) assert(node.kind == JArray) return node.elems[index] @@ -671,6 +713,23 @@ proc `[]=`*(obj: PJsonNode, key: string, val: PJsonNode) = return obj.fields.add((key, val)) +proc `{}`*(node: PJsonNode, key: string): PJsonNode = + ## Transverses the node and gets the given value. If any of the + ## names does not exist, returns nil + result = node + if isNil(node): return nil + result = result[key] + +proc `{}=`*(node: PJsonNode, names: varargs[string], value: PJsonNode) = + ## Transverses the node and tries to set the value at the given location + ## to `value` If any of the names are missing, they are added + var node = node + for i in 0..(names.len-2): + if isNil(node[names[i]]): + node[names[i]] = newJObject() + node = node[names[i]] + node[names[names.len-1]] = value + proc delete*(obj: PJsonNode, key: string) = ## Deletes ``obj[key]`` preserving the order of the other (key, value)-pairs. assert(obj.kind == JObject) @@ -996,6 +1055,28 @@ when isMainModule: raise newException(EInvalidValue, "That line was expected to fail") except EInvalidIndex: echo() + let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }""" + # nil passthrough + assert(testJson{"doesnt_exist"}{"anything"}.isNil) + testJson{["c", "d"]} = %true + assert(testJson["c"]["d"].bval) + + # Bounds checking + try: + let a = testJson["a"][9] + assert(false, "EInvalidIndex not thrown") + except EInvalidIndex: + discard + try: + let a = testJson["a"][-1] + assert(false, "EInvalidIndex not thrown") + except EInvalidIndex: + discard + try: + assert(testJson["a"][0].num == 1, "Index doesn't correspond to its value") + except: + assert(false, "EInvalidIndex thrown for valid index") + discard """ while true: var json = stdin.readLine() diff --git a/lib/pure/math.nim b/lib/pure/math.nim index e4aecd272..2f7a696b9 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -10,7 +10,8 @@ ## Constructive mathematics is naturally typed. -- Simon Thompson ## ## Basic math routines for Nimrod. -## This module is available for the JavaScript target. +## This module is available for the `JavaScript target +## <backends.html#the-javascript-target>`_. include "system/inclrtl" @@ -135,12 +136,12 @@ proc random*(max: int): int {.gcsafe.} ## which initializes the random number generator with a "random" ## number, i.e. a tickcount. -when not defined(windows): - proc random*(max: float): float {.gcsafe.} - ## returns a random number in the range 0..<max. The sequence of - ## random number is always the same, unless `randomize` is called - ## which initializes the random number generator with a "random" - ## number, i.e. a tickcount. This is currently not supported for windows. +proc random*(max: float): float {.gcsafe.} + ## returns a random number in the range 0..<max. The sequence of + ## random number is always the same, unless `randomize` is called + ## which initializes the random number generator with a "random" + ## number, i.e. a tickcount. This has a 16-bit resolution on windows + ## and a 48-bit resolution on other platforms. proc randomize*() {.gcsafe.} ## initializes the random number generator with a "random" @@ -205,7 +206,14 @@ when not defined(JS): proc drand48(): float {.importc: "drand48", header: "<stdlib.h>".} proc random(max: float): float = result = drand48() * max - + when defined(windows): + proc random(max: float): float = + # we are hardcodeing this because + # importcing macros is extremely problematic + # and because the value is publicly documented + # on MSDN and very unlikely to change + const rand_max = 32767 + result = (float(rand()) / float(rand_max)) * max proc randomize() = randomize(cast[int](epochTime())) diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 807f3da43..31fefc6c8 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -74,9 +74,22 @@ proc unmapMem*(f: var TMemFile, p: pointer, size: int) = proc open*(filename: string, mode: TFileMode = fmRead, mappedSize = -1, offset = 0, newFileSize = -1): TMemFile = ## opens a memory mapped file. If this fails, ``EOS`` is raised. - ## `newFileSize` can only be set if the file is not opened with ``fmRead`` - ## access. `mappedSize` and `offset` can be used to map only a slice of - ## the file. + ## `newFileSize` can only be set if the file does not exist and is opened + ## with write access (e.g., with fmReadWrite). `mappedSize` and `offset` + ## can be used to map only a slice of the file. Example: + ## + ## .. code-block:: nimrod + ## var + ## mm, mm_full, mm_half: TMemFile + ## + ## mm = memfiles.open("/tmp/test.mmap", mode = fmWrite, newFileSize = 1024) # Create a new file + ## mm.close() + ## + ## # Read the whole file, would fail if newFileSize was set + ## mm_full = memfiles.open("/tmp/test.mmap", mode = fmReadWrite, mappedSize = -1) + ## + ## # Read the first 512 bytes + ## mm_half = memfiles.open("/tmp/test.mmap", mode = fmReadWrite, mappedSize = 512) # The file can be resized only when write mode is used: assert newFileSize == -1 or mode != fmRead @@ -165,8 +178,11 @@ proc open*(filename: string, mode: TFileMode = fmRead, if newFileSize != -1: flags = flags or O_CREAT or O_TRUNC + var permissions_mode = S_IRUSR or S_IWUSR + result.handle = open(filename, flags, permissions_mode) + else: + result.handle = open(filename, flags) - result.handle = open(filename, flags) if result.handle == -1: # XXX: errno is supposed to be set here # Is there an exception that wraps it? diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 74739630b..ddc2bbe2d 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -11,7 +11,7 @@ {.deadCodeElim: on.} import rawsockets, os, strutils, unsigned, parseutils, times -export TPort +export TPort, `$` const useWinVersion = defined(Windows) or defined(nimdoc) @@ -350,6 +350,30 @@ type ETimeout* = object of ESynch + TSocketFlags* {.pure.} = enum + Peek, + SafeDisconn ## Ensures disconnection exceptions (ECONNRESET, EPIPE etc) are not thrown. + +proc isDisconnectionError*(flags: set[TSocketFlags], + lastError: TOSErrorCode): bool = + ## Determines whether ``lastError`` is a disconnection error. Only does this + ## if flags contains ``SafeDisconn``. + when useWinVersion: + TSocketFlags.SafeDisconn in flags and + lastError.int32 in {WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, + WSAEDISCON} + else: + TSocketFlags.SafeDisconn in flags and + lastError.int32 in {ECONNRESET, EPIPE, ENETRESET} + +proc toOSFlags*(socketFlags: set[TSocketFlags]): cint = + ## Converts the flags into the underlying OS representation. + for f in socketFlags: + case f + of TSocketFlags.Peek: + result = result or MSG_PEEK + of TSocketFlags.SafeDisconn: continue + proc createSocket(fd: TSocketHandle, isBuff: bool): PSocket = assert fd != osInvalidSocket new(result) @@ -470,7 +494,8 @@ when defined(ssl): if SSLSetFd(socket.sslHandle, socket.fd) != 1: SSLError() -proc socketError*(socket: PSocket, err: int = -1, async = false) = +proc socketError*(socket: PSocket, err: int = -1, async = false, + lastError = (-1).TOSErrorCode) = ## Raises an EOS error based on the error code returned by ``SSLGetError`` ## (for SSL sockets) and ``osLastError`` otherwise. ## @@ -500,17 +525,17 @@ proc socketError*(socket: PSocket, err: int = -1, async = false) = else: SSLError("Unknown Error") if err == -1 and not (when defined(ssl): socket.isSSL else: false): - let lastError = osLastError() + let lastE = if lastError.int == -1: osLastError() else: lastError if async: when useWinVersion: - if lastError.int32 == WSAEWOULDBLOCK: + if lastE.int32 == WSAEWOULDBLOCK: return - else: osError(lastError) + else: osError(lastE) else: - if lastError.int32 == EAGAIN or lastError.int32 == EWOULDBLOCK: + if lastE.int32 == EAGAIN or lastE.int32 == EWOULDBLOCK: return - else: osError(lastError) - else: osError(lastError) + else: osError(lastE) + else: osError(lastE) proc listen*(socket: PSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = ## Marks ``socket`` as accepting connections. @@ -805,6 +830,7 @@ proc recv*(socket: PSocket, data: pointer, size: int): int {.tags: [FReadIO].} = let chunk = min(socket.bufLen-socket.currPos, size-read) var d = cast[cstring](data) + assert size-read >= chunk copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk) read.inc(chunk) socket.currPos.inc(chunk) @@ -871,6 +897,7 @@ proc recv*(socket: PSocket, data: pointer, size: int, timeout: int): int {. while read < size: let avail = waitFor(socket, waited, timeout, size-read, "recv") var d = cast[cstring](data) + assert avail <= size-read result = recv(socket, addr(d[read]), avail) if result == 0: break if result < 0: @@ -879,7 +906,8 @@ proc recv*(socket: PSocket, data: pointer, size: int, timeout: int): int {. result = read -proc recv*(socket: PSocket, data: var string, size: int, timeout = -1): int = +proc recv*(socket: PSocket, data: var string, size: int, timeout = -1, + flags = {TSocketFlags.SafeDisconn}): int = ## Higher-level version of ``recv``. ## ## When 0 is returned the socket's connection has been closed. @@ -891,11 +919,15 @@ proc recv*(socket: PSocket, data: var string, size: int, timeout = -1): int = ## within the time specified an ETimeout exception will be raised. ## ## **Note**: ``data`` must be initialised. + ## + ## **Warning**: Only the ``SafeDisconn`` flag is currently supported. data.setLen(size) result = recv(socket, cstring(data), size, timeout) if result < 0: data.setLen(0) - socket.socketError(result) + let lastError = osLastError() + if flags.isDisconnectionError(lastError): return + socket.socketError(result, lastError = lastError) data.setLen(result) proc peekChar(socket: PSocket, c: var char): int {.tags: [FReadIO].} = @@ -918,7 +950,8 @@ proc peekChar(socket: PSocket, c: var char): int {.tags: [FReadIO].} = return result = recv(socket.fd, addr(c), 1, MSG_PEEK) -proc readLine*(socket: PSocket, line: var TaintedString, timeout = -1) {. +proc readLine*(socket: PSocket, line: var TaintedString, timeout = -1, + flags = {TSocketFlags.SafeDisconn}) {. tags: [FReadIO, FTime].} = ## Reads a line of data from ``socket``. ## @@ -932,11 +965,18 @@ proc readLine*(socket: PSocket, line: var TaintedString, timeout = -1) {. ## ## A timeout can be specified in miliseconds, if data is not received within ## the specified time an ETimeout exception will be raised. + ## + ## **Warning**: Only the ``SafeDisconn`` flag is currently supported. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") + template raiseSockError(): stmt {.dirty, immediate.} = + let lastError = osLastError() + if flags.isDisconnectionError(lastError): setLen(line.string, 0); return + socket.socketError(n, lastError = lastError) + var waited = 0.0 setLen(line.string, 0) @@ -944,14 +984,14 @@ proc readLine*(socket: PSocket, line: var TaintedString, timeout = -1) {. var c: char discard waitFor(socket, waited, timeout, 1, "readLine") var n = recv(socket, addr(c), 1) - if n < 0: socket.socketError() - elif n == 0: return + if n < 0: raiseSockError() + elif n == 0: setLen(line.string, 0); return if c == '\r': discard waitFor(socket, waited, timeout, 1, "readLine") n = peekChar(socket, c) if n > 0 and c == '\L': discard recv(socket, addr(c), 1) - elif n <= 0: socket.socketError() + elif n <= 0: raiseSockError() addNLIfEmpty() return elif c == '\L': @@ -1019,11 +1059,14 @@ proc send*(socket: PSocket, data: pointer, size: int): int {. const MSG_NOSIGNAL = 0 result = send(socket.fd, data, size, int32(MSG_NOSIGNAL)) -proc send*(socket: PSocket, data: string) {.tags: [FWriteIO].} = +proc send*(socket: PSocket, data: string, + flags = {TSocketFlags.SafeDisconn}) {.tags: [FWriteIO].} = ## sends data to a socket. let sent = send(socket, cstring(data), data.len) if sent < 0: - socketError(socket) + let lastError = osLastError() + if flags.isDisconnectionError(lastError): return + socketError(socket, lastError = lastError) if sent != data.len: raise newException(EOS, "Could not send all data.") diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim index 3d0cc2154..ab7cd1944 100644 --- a/lib/pure/nimprof.nim +++ b/lib/pure/nimprof.nim @@ -58,8 +58,9 @@ when not defined(memProfiler): ## instruction count measure instead then. if intervalInUs <= 0: interval = 0 else: interval = intervalInUs * 1000 - tickCountCorrection - + when withThreads: + import locks var profilingLock: TLock @@ -72,7 +73,7 @@ proc hookAux(st: TStackTrace, costs: int) = var last = high(st) while last > 0 and isNil(st[last]): dec last var h = hash(pointer(st[last])) and high(profileData) - + # we use probing for maxChainLen entries and replace the encountered entry # with the minimal 'total' value: if emptySlots == 0: @@ -133,7 +134,7 @@ else: hookAux(st, 1) elif getticks() - t0 > interval: hookAux(st, 1) - t0 = getticks() + t0 = getticks() proc getTotal(x: ptr TProfileEntry): int = result = if isNil(x): 0 else: x.total @@ -145,7 +146,7 @@ proc `//`(a, b: int): string = result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2)) proc writeProfile() {.noconv.} = - when defined(system.TStackTrace): + when defined(system.TStackTrace): system.profilerHook = nil const filename = "profile_results.txt" echo "writing " & filename & "..." @@ -156,7 +157,7 @@ proc writeProfile() {.noconv.} = var entries = 0 for i in 0..high(profileData): if profileData[i] != nil: inc entries - + var perProc = initCountTable[string]() for i in 0..entries-1: var dups = initSet[string]() @@ -166,7 +167,7 @@ proc writeProfile() {.noconv.} = let p = $procname if not containsOrIncl(dups, p): perProc.inc(p, profileData[i].total) - + var sum = 0 # only write the first 100 entries: for i in 0..min(100, entries-1): diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim index b3e74d2a1..2843e6c65 100644 --- a/lib/pure/oids.nim +++ b/lib/pure/oids.nim @@ -62,9 +62,9 @@ var proc genOid*(): TOid = ## generates a new OID. - proc rand(): cint {.importc: "rand", nodecl.} + proc rand(): cint {.importc: "rand", header: "<stdlib.h>", nodecl.} proc gettime(dummy: ptr cint): cint {.importc: "time", header: "<time.h>".} - proc srand(seed: cint) {.importc: "srand", nodecl.} + proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>", nodecl.} var t = gettime(nil) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 00a33db75..0b4538abc 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -1612,6 +1612,20 @@ when defined(linux) or defined(solaris) or defined(bsd) or defined(aix): len = readlink(procPath, result, len) setLen(result, len) +when not (defined(windows) or defined(macosx)): + proc getApplHeuristic(): string = + when defined(paramStr): + result = string(paramStr(0)) + # POSIX guaranties that this contains the executable + # as it has been executed by the calling process + if len(result) > 0 and result[0] != DirSep: # not an absolute path? + # iterate over any path in the $PATH environment variable + for p in split(string(getEnv("PATH")), {PathSep}): + var x = joinPath(p, result) + if existsFile(x): return x + else: + result = "" + when defined(macosx): type cuint32* {.importc: "unsigned int", nodecl.} = int @@ -1648,10 +1662,13 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} = setlen(result, int(len)) elif defined(linux) or defined(aix): result = getApplAux("/proc/self/exe") + if result.len == 0: result = getApplHeuristic() elif defined(solaris): result = getApplAux("/proc/" & $getpid() & "/path/a.out") + if result.len == 0: result = getApplHeuristic() elif defined(freebsd): result = getApplAux("/proc/" & $getpid() & "/file") + if result.len == 0: result = getApplHeuristic() elif defined(macosx): var size: cuint32 getExecPath1(nil, size) @@ -1663,15 +1680,7 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} = else: # little heuristic that may work on other POSIX-like systems: result = string(getEnv("_")) - if len(result) == 0: - result = string(paramStr(0)) - # POSIX guaranties that this contains the executable - # as it has been executed by the calling process - if len(result) > 0 and result[0] != DirSep: # not an absolute path? - # iterate over any path in the $PATH environment variable - for p in split(string(getEnv("PATH")), {PathSep}): - var x = joinPath(p, result) - if existsFile(x): return x + if result.len == 0: result = getApplHeuristic() proc getApplicationFilename*(): string {.rtl, extern: "nos$1", deprecated.} = ## Returns the filename of the application's executable. @@ -1760,16 +1769,16 @@ else: FileId = TIno type - FileInfo = object + FileInfo* = object ## Contains information associated with a file object. - id: tuple[device: DeviceId, file: FileId] # Device and file id. - kind: TPathComponent # Kind of file object - directory, symlink, etc. - size: BiggestInt # Size of file. - permissions: set[TFilePermission] # File permissions - linkCount: BiggestInt # Number of hard links the file object has. - lastAccessTime: TTime # Time file was last accessed. - lastWriteTime: TTime # Time file was last modified/written to. - creationTime: TTime # Time file was created. Not supported on all systems! + id*: tuple[device: DeviceId, file: FileId] # Device and file id. + kind*: TPathComponent # Kind of file object - directory, symlink, etc. + size*: BiggestInt # Size of file. + permissions*: set[TFilePermission] # File permissions + linkCount*: BiggestInt # Number of hard links the file object has. + lastAccessTime*: TTime # Time file was last accessed. + lastWriteTime*: TTime # Time file was last modified/written to. + creationTime*: TTime # Time file was created. Not supported on all systems! template rawToFormalFileInfo(rawInfo, formalInfo): expr = ## Transforms the native file info structure into the one nimrod uses. diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 6e250f9d5..04a0c2403 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2013 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -13,7 +13,7 @@ include "system/inclrtl" import - strutils, os, strtabs, streams + strutils, os, strtabs, streams, cpuinfo when defined(windows): import winlean @@ -225,42 +225,10 @@ proc errorHandle*(p: PProcess): TFileHandle {.rtl, extern: "nosp$1", ## it is closed when closing the PProcess ``p``. result = p.errHandle -when defined(macosx) or defined(bsd): - const - CTL_HW = 6 - HW_AVAILCPU = 25 - HW_NCPU = 3 - proc sysctl(x: ptr array[0..3, cint], y: cint, z: pointer, - a: var csize, b: pointer, c: int): cint {. - importc: "sysctl", header: "<sys/sysctl.h>".} - proc countProcessors*(): int {.rtl, extern: "nosp$1".} = ## returns the numer of the processors/cores the machine has. ## Returns 0 if it cannot be detected. - when defined(windows): - var x = getEnv("NUMBER_OF_PROCESSORS") - if x.len > 0: result = parseInt(x.string) - elif defined(macosx) or defined(bsd): - var - mib: array[0..3, cint] - numCPU: int - len: csize - mib[0] = CTL_HW - mib[1] = HW_AVAILCPU - len = sizeof(numCPU) - discard sysctl(addr(mib), 2, addr(numCPU), len, nil, 0) - if numCPU < 1: - mib[1] = HW_NCPU - discard sysctl(addr(mib), 2, addr(numCPU), len, nil, 0) - result = numCPU - elif defined(hpux): - result = mpctl(MPC_GETNUMSPUS, nil, nil) - elif defined(irix): - var SC_NPROC_ONLN {.importc: "_SC_NPROC_ONLN", header: "<unistd.h>".}: cint - result = sysconf(SC_NPROC_ONLN) - else: - result = sysconf(SC_NPROCESSORS_ONLN) - if result <= 0: result = 1 + result = cpuinfo.countProcessors() proc execProcesses*(cmds: openArray[string], options = {poStdErrToStdOut, poParentStreams}, @@ -800,8 +768,8 @@ elif not defined(useNimRtl): proc startProcessAfterFork(data: ptr TStartProcessData) = # Warning: no GC here! - # Or anythink that touches global structures - all called nimrod procs - # must be marked with noStackFrame. Inspect C code after making changes. + # Or anything that touches global structures - all called nimrod procs + # must be marked with stackTrace:off. Inspect C code after making changes. if not data.optionPoParentStreams: discard close(data.pStdin[writeIdx]) if dup2(data.pStdin[readIdx], readIdx) < 0: @@ -903,7 +871,7 @@ elif not defined(useNimRtl): createStream(p.errStream, p.errHandle, fmRead) return p.errStream - proc csystem(cmd: cstring): cint {.nodecl, importc: "system".} + proc csystem(cmd: cstring): cint {.nodecl, importc: "system", header: "<stdlib.h>".} proc execCmd(command: string): int = when defined(linux): diff --git a/lib/pure/rawsockets.nim b/lib/pure/rawsockets.nim index 07b647b68..d96741846 100644 --- a/lib/pure/rawsockets.nim +++ b/lib/pure/rawsockets.nim @@ -21,11 +21,12 @@ const useWinVersion = defined(Windows) or defined(nimdoc) when useWinVersion: import winlean - export WSAEWOULDBLOCK + export WSAEWOULDBLOCK, WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, + WSAEDISCON else: import posix export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL, - EINTR, EINPROGRESS + EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen, inet_ntoa, recv, `==`, connect, send, accept, recvfrom, sendto @@ -39,7 +40,6 @@ export MSG_PEEK type - TPort* = distinct uint16 ## port type TDomain* = enum ## domain, which specifies the protocol family of the diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index f630ba235..bd53c2dbf 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -11,9 +11,12 @@ import tables, os, unsigned, hashes -when defined(linux): import posix, epoll -elif defined(windows): import winlean -else: import posix +when defined(linux): + import posix, epoll +elif defined(windows): + import winlean +else: + import posix proc hash*(x: TSocketHandle): THash {.borrow.} proc `$`*(x: TSocketHandle): string {.borrow.} @@ -29,7 +32,36 @@ type TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]] -when defined(linux) or defined(nimdoc): +when defined(nimdoc): + type + PSelector* = ref object + ## An object which holds file descripters to be checked for read/write + ## status. + fds: TTable[TSocketHandle, PSelectorKey] + + proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], + data: PObject): PSelectorKey {.discardable.} = + ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent + ## ``events``. + + proc update*(s: PSelector, fd: TSocketHandle, + events: set[TEvent]): PSelectorKey {.discardable.} = + ## Updates the events which ``fd`` wants notifications for. + + proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = + ## The ``events`` field of the returned ``key`` contains the original events + ## for which the ``fd`` was bound. This is contrary to the ``events`` field + ## of the ``TReadyInfo`` tuple which determines which events are ready + ## on the ``fd``. + + proc contains*(s: PSelector, fd: TSocketHandle): bool = + ## Determines whether selector contains a file descriptor. + + proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey = + ## Retrieves the selector key for ``fd``. + + +elif defined(linux): type PSelector* = ref object epollFD: cint @@ -49,9 +81,10 @@ when defined(linux) or defined(nimdoc): ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent ## ``events``. var event = createEventStruct(events, fd) - if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: - OSError(OSLastError()) - + if events != {}: + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: + OSError(OSLastError()) + var key = PSelectorKey(fd: fd, events: events, data: data) s.fds[fd] = key @@ -61,11 +94,27 @@ when defined(linux) or defined(nimdoc): events: set[TEvent]): PSelectorKey {.discardable.} = ## Updates the events which ``fd`` wants notifications for. if s.fds[fd].events != events: - var event = createEventStruct(events, fd) + if events == {}: + # This fd is idle -- it should not be registered to epoll. + # But it should remain a part of this selector instance. + # This is to prevent epoll_wait from returning immediately + # because its got fds which are waiting for no events and + # are therefore constantly ready. (leading to 100% CPU usage). + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: + OSError(OSLastError()) + s.fds[fd].events = events + else: + var event = createEventStruct(events, fd) + if s.fds[fd].events == {}: + # This fd is idle. It's not a member of this epoll instance and must + # be re-registered. + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: + OSError(OSLastError()) + else: + if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: + OSError(OSLastError()) + s.fds[fd].events = events - s.fds[fd].events = events - if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: - OSError(OSLastError()) result = s.fds[fd] proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} = @@ -114,7 +163,7 @@ when defined(linux) or defined(nimdoc): proc newSelector*(): PSelector = new result result.epollFD = epoll_create(64) - result.events = cast[array[64, epoll_event]](alloc0(sizeof(epoll_event)*64)) + #result.events = cast[array[64, epoll_event]](alloc0(sizeof(epoll_event)*64)) result.fds = initTable[TSocketHandle, PSelectorKey]() if result.epollFD < 0: OSError(OSLastError()) @@ -123,7 +172,10 @@ when defined(linux) or defined(nimdoc): ## Determines whether selector contains a file descriptor. if s.fds.hasKey(fd): # Ensure the underlying epoll instance still contains this fd. - result = epollHasFd(s, fd) + if s.fds[fd].events != {}: + result = epollHasFd(s, fd) + else: + result = true else: return false @@ -131,7 +183,7 @@ when defined(linux) or defined(nimdoc): ## Retrieves the selector key for ``fd``. return s.fds[fd] -else: +elif not defined(nimdoc): # TODO: kqueue for bsd/mac os x. type PSelector* = ref object @@ -230,7 +282,7 @@ proc contains*(s: PSelector, key: PSelectorKey): bool = ## the new one may have the same value. return key.fd in s and s.fds[key.fd] == key -when isMainModule: +when isMainModule and not defined(nimdoc): # Select() import sockets type diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 8d96cbaaf..7b8b3d557 100644 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -295,7 +295,7 @@ when defined(ssl): of protSSLv23: newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support. of protSSLv2: - when not defined(linux): + when not defined(linux) and not defined(OpenBSD): newCTX = SSL_CTX_new(SSLv2_method()) else: SSLError() diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index bd6814dcc..e642f6a99 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -10,6 +10,8 @@ ## This module contains various string utility routines. ## See the module `re <re.html>`_ for regular expression support. ## See the module `pegs <pegs.html>`_ for PEG support. +## This module is available for the `JavaScript target +## <backends.html#the-javascript-target>`_. import parseutils diff --git a/lib/pure/times.nim b/lib/pure/times.nim index fdff06b2a..498511899 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -9,7 +9,8 @@ ## This module contains routines and types for dealing with time. -## This module is available for the JavaScript target. +## This module is available for the `JavaScript target +## <backends.html#the-javascript-target>`_. {.push debugger:off.} # the user does not want to trace a part # of the standard library! |