summary refs log tree commit diff stats
path: root/lib/pure
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure')
-rw-r--r--lib/pure/concurrency/threadpool.nim10
-rw-r--r--lib/pure/coro.nim145
-rw-r--r--lib/pure/os.nim6
-rw-r--r--lib/pure/osproc.nim12
-rw-r--r--lib/pure/uri.nim27
5 files changed, 188 insertions, 12 deletions
diff --git a/lib/pure/concurrency/threadpool.nim b/lib/pure/concurrency/threadpool.nim
index 7c9d8adfd..0079cf302 100644
--- a/lib/pure/concurrency/threadpool.nim
+++ b/lib/pure/concurrency/threadpool.nim
@@ -322,6 +322,9 @@ var
   distinguished: array[MaxDistinguishedThread, TThread[ptr Worker]]
   distinguishedData: array[MaxDistinguishedThread, Worker]
 
+when defined(nimPinToCpu):
+  var gCpus: Natural
+
 proc setMinPoolSize*(size: range[1..MaxThreadPoolSize]) =
   ## sets the minimal thread pool size. The default value of this is 4.
   minPoolSize = size
@@ -342,6 +345,8 @@ proc activateWorkerThread(i: int) {.noinline.} =
   workersData[i].q.empty = createSemaphore()
   initLock(workersData[i].q.lock)
   createThread(workers[i], slave, addr(workersData[i]))
+  when defined(nimPinToCpu):
+    if gCpus > 0: pinToCpu(workers[i], i mod gCpus)
 
 proc activateDistinguishedThread(i: int) {.noinline.} =
   distinguishedData[i].taskArrived = createSemaphore()
@@ -353,7 +358,10 @@ proc activateDistinguishedThread(i: int) {.noinline.} =
   createThread(distinguished[i], distinguishedSlave, addr(distinguishedData[i]))
 
 proc setup() =
-  currentPoolSize = min(countProcessors(), MaxThreadPoolSize)
+  let p = countProcessors()
+  when defined(nimPinToCpu):
+    gCpus = p
+  currentPoolSize = min(p, MaxThreadPoolSize)
   readyWorker = addr(workersData[0])
   for i in 0.. <currentPoolSize: activateWorkerThread(i)
 
diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim
new file mode 100644
index 000000000..6ef5f6f54
--- /dev/null
+++ b/lib/pure/coro.nim
@@ -0,0 +1,145 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Rokas Kupstys
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+when not defined(nimCoroutines):
+  {.error: "Coroutines require -d:nimCoroutines".}
+
+import os, times
+import macros
+import arch
+import lists
+
+const coroDefaultStackSize = 512 * 1024
+
+
+type Coroutine = ref object
+  # prev: ptr Coroutine
+  # next: ptr Coroutine
+  ctx: JmpBuf
+  fn: proc()
+  started: bool
+  lastRun: float
+  sleepTime: float
+  stack: pointer
+  stacksize: int
+
+var coroutines = initDoublyLinkedList[Coroutine]()
+var current: Coroutine
+var mainCtx: JmpBuf
+
+
+proc GC_addStack(starts: pointer) {.cdecl, importc.}
+proc GC_removeStack(starts: pointer) {.cdecl, importc.}
+proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.}
+
+
+proc coroStart*(c: proc(), stacksize: int=coroDefaultStackSize) =
+  ## Adds coroutine to event loop. It does not run immediately.
+  var coro = Coroutine()
+  coro.fn = c
+  while coro.stack == nil:
+    coro.stack = alloc0(stacksize)
+  coro.stacksize = stacksize
+  coroutines.append(coro)
+
+{.push stackTrace: off.}
+proc coroYield*(sleepTime: float=0) =
+  ## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds.
+  ## Until then other coroutines are executed.
+  var oldFrame = getFrame()
+  var sp {.volatile.}: pointer
+  GC_setCurrentStack(current.stack, cast[pointer](addr sp))
+  current.sleepTime = sleep_time
+  current.lastRun = epochTime()
+  if setjmp(current.ctx) == 0:
+    longjmp(mainCtx, 1)
+  setFrame(oldFrame)
+{.pop.}
+
+proc coroRun*() =
+  ## Starts main event loop which exits when all coroutines exit. Calling this proc
+  ## starts execution of first coroutine.
+  var node = coroutines.head
+  var minDelay: float = 0
+  var frame: PFrame
+  while node != nil:
+    var coro = node.value
+    current = coro
+    os.sleep(int(minDelay * 1000))
+
+    var remaining = coro.sleepTime - (epochTime() - coro.lastRun);
+    if remaining <= 0:
+      remaining = 0
+      let res = setjmp(mainCtx)
+      if res == 0:
+        frame = getFrame()
+        if coro.started:            # coroutine resumes
+          longjmp(coro.ctx, 1)
+        else:
+          coro.started = true       # coroutine starts
+          var stackEnd = cast[pointer](cast[ByteAddress](coro.stack) + coro.stacksize)
+          GC_addStack(coro.stack)
+          coroSwitchStack(stackEnd)
+          coro.fn()
+          coroRestoreStack()
+          GC_removeStack(coro.stack)
+          var next = node.prev
+          coroutines.remove(node)
+          dealloc(coro.stack)
+          node = next
+          setFrame(frame)
+      else:
+        setFrame(frame)
+
+    elif remaining > 0:
+      if minDelay > 0 and remaining > 0:
+        minDelay = min(remaining, minDelay)
+      else:
+        minDelay = remaining
+
+    if node == nil or node.next == nil:
+      node = coroutines.head
+    else:
+      node = node.next
+
+
+proc coroAlive*(c: proc()): bool =
+  ## Returns ``true`` if coroutine has not returned, ``false`` otherwise.
+  for coro in items(coroutines):
+    if coro.fn == c:
+      return true
+
+proc coroWait*(c: proc(), interval=0.01) =
+  ## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often.
+  while coroAlive(c):
+    coroYield interval
+
+
+when isMainModule:
+  var stackCheckValue = 1100220033
+  proc c2()
+
+  proc c1() =
+    for i in 0 .. 3:
+      echo "c1"
+      coroYield 0.05
+    echo "c1 exits"
+
+
+  proc c2() =
+    for i in 0 .. 3:
+      echo "c2"
+      coroYield 0.025
+    coroWait(c1)
+    echo "c2 exits"
+
+  coroStart(c1)
+  coroStart(c2)
+  coroRun()
+  echo "done ", stackCheckValue
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index c2c28c2b1..3d592a526 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -461,8 +461,10 @@ proc getLastAccessTime*(file: string): Time {.rtl, extern: "nos$1".} =
 
 proc getCreationTime*(file: string): Time {.rtl, extern: "nos$1".} =
   ## Returns the `file`'s creation time.
-  ## Note that under posix OS's, the returned time may actually be the time at
-  ## which the file's attribute's were last modified.
+  ##
+  ## **Note:** Under POSIX OS's, the returned time may actually be the time at
+  ## which the file's attribute's were last modified. See
+  ## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details.
   when defined(posix):
     var res: Stat
     if stat(file, res) < 0'i32: raiseOSError(osLastError())
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim
index add4bc0a8..81befccff 100644
--- a/lib/pure/osproc.nim
+++ b/lib/pure/osproc.nim
@@ -468,7 +468,14 @@ when defined(Windows) and not defined(useNimRtl):
         fileClose(si.hStdError)
 
     if e != nil: dealloc(e)
-    if success == 0: raiseOSError(lastError, command)
+    if success == 0:
+      const errInvalidParameter = 87.int
+      const errFileNotFound = 2.int
+      if lastError.int in {errInvalidParameter, errFileNotFound}:
+        raiseOSError(lastError,
+            "Requested command not found: '$1'. OS error:" % command)
+      else:
+        raiseOSError(lastError, command)
     # Close the handle now so anyone waiting is woken:
     discard closeHandle(procInfo.hThread)
     result.fProcessHandle = procInfo.hProcess
@@ -778,7 +785,8 @@ elif not defined(useNimRtl):
     var error: cint
     let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error))
     if sizeRead == sizeof(error):
-      raiseOSError($strerror(error))
+      raiseOSError("Could not find command: '$1'. OS error: $2" %
+          [$data.sysCommand, $strerror(error)])
 
     return pid
 
diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim
index 1890a9bf4..492de3b46 100644
--- a/lib/pure/uri.nim
+++ b/lib/pure/uri.nim
@@ -14,7 +14,7 @@ type
   Url* = distinct string
 
   Uri* = object
-    scheme*, username*, password*: string 
+    scheme*, username*, password*: string
     hostname*, port*, path*, query*, anchor*: string
     opaque*: bool
 
@@ -69,7 +69,7 @@ proc parseAuthority(authority: string, result: var Uri) =
     i.inc
 
 proc parsePath(uri: string, i: var int, result: var Uri) =
-  
+
   i.inc parseUntil(uri, result.path, {'?', '#'}, i)
 
   # The 'mailto' scheme's PATH actually contains the hostname/username
@@ -104,19 +104,23 @@ proc parseUri*(uri: string, result: var Uri) =
   var i = 0
 
   # Check if this is a reference URI (relative URI)
+  let doubleSlash = uri.len > 1 and uri[1] == '/'
   if uri[i] == '/':
-    parsePath(uri, i, result)
-    return
+    # Make sure ``uri`` doesn't begin with '//'.
+    if not doubleSlash:
+      parsePath(uri, i, result)
+      return
 
   # Scheme
   i.inc parseWhile(uri, result.scheme, Letters + Digits + {'+', '-', '.'}, i)
-  if uri[i] != ':':
+  if uri[i] != ':' and not doubleSlash:
     # Assume this is a reference URI (relative URI)
     i = 0
     result.scheme.setLen(0)
     parsePath(uri, i, result)
     return
-  i.inc # Skip ':'
+  if not doubleSlash:
+    i.inc # Skip ':'
 
   # Authority
   if uri[i] == '/' and uri[i+1] == '/':
@@ -201,7 +205,7 @@ proc combine*(base: Uri, reference: Uri): Uri =
   ##
   ##   let bar = combine(parseUri("http://example.com/foo/bar/"), parseUri("baz"))
   ##   assert bar.path == "/foo/bar/baz"
-  
+
   template setAuthority(dest, src: expr): stmt =
     dest.hostname = src.hostname
     dest.username = src.username
@@ -369,6 +373,15 @@ when isMainModule:
     doAssert test.path == "test/no/slash"
     doAssert($test == str)
 
+  block:
+    let str = "//git@github.com:dom96/packages"
+    let test = parseUri(str)
+    doAssert test.scheme == ""
+    doAssert test.username == "git"
+    doAssert test.hostname == "github.com"
+    doAssert test.port == "dom96"
+    doAssert test.path == "/packages"
+
   # Remove dot segments tests
   block:
     doAssert removeDotSegments("/foo/bar/baz") == "/foo/bar/baz"