coro.nim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Rokas Kupstys
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Nim coroutines implementation, supports several context switching methods:
  10. ## ======== ============
  11. ## ucontext available on unix and alike (default)
  12. ## setjmp available on unix and alike (x86/64 only)
  13. ## fibers available and required on windows.
  14. ## ======== ============
  15. ##
  16. ## -d:nimCoroutines Required to build this module.
  17. ## -d:nimCoroutinesUcontext Use ucontext backend.
  18. ## -d:nimCoroutinesSetjmp Use setjmp backend.
  19. ## -d:nimCoroutinesSetjmpBundled Use bundled setjmp implementation.
  20. ##
  21. ## Unstable API.
  22. import system/coro_detection
  23. when not nimCoroutines and not defined(nimdoc):
  24. when defined(noNimCoroutines):
  25. {.error: "Coroutines can not be used with -d:noNimCoroutines".}
  26. else:
  27. {.error: "Coroutines require -d:nimCoroutines".}
  28. import os
  29. import lists
  30. include system/timers
  31. const defaultStackSize = 512 * 1024
  32. const useOrcArc = defined(gcArc) or defined(gcOrc)
  33. when useOrcArc:
  34. proc nimGC_setStackBottom*(theStackBottom: pointer) = discard
  35. proc GC_addStack(bottom: pointer) {.cdecl, importc.}
  36. proc GC_removeStack(bottom: pointer) {.cdecl, importc.}
  37. proc GC_setActiveStack(bottom: pointer) {.cdecl, importc.}
  38. proc GC_getActiveStack() : pointer {.cdecl, importc.}
  39. const
  40. CORO_BACKEND_UCONTEXT = 0
  41. CORO_BACKEND_SETJMP = 1
  42. CORO_BACKEND_FIBERS = 2
  43. when defined(windows):
  44. const coroBackend = CORO_BACKEND_FIBERS
  45. when defined(nimCoroutinesUcontext):
  46. {.warning: "ucontext coroutine backend is not available on windows, defaulting to fibers.".}
  47. when defined(nimCoroutinesSetjmp):
  48. {.warning: "setjmp coroutine backend is not available on windows, defaulting to fibers.".}
  49. elif defined(haiku) or defined(openbsd):
  50. const coroBackend = CORO_BACKEND_SETJMP
  51. when defined(nimCoroutinesUcontext):
  52. {.warning: "ucontext coroutine backend is not available on haiku, defaulting to setjmp".}
  53. elif defined(nimCoroutinesSetjmp) or defined(nimCoroutinesSetjmpBundled):
  54. const coroBackend = CORO_BACKEND_SETJMP
  55. else:
  56. const coroBackend = CORO_BACKEND_UCONTEXT
  57. when coroBackend == CORO_BACKEND_FIBERS:
  58. import windows/winlean
  59. type
  60. Context = pointer
  61. elif coroBackend == CORO_BACKEND_UCONTEXT:
  62. type
  63. stack_t {.importc, header: "<ucontext.h>".} = object
  64. ss_sp: pointer
  65. ss_flags: int
  66. ss_size: int
  67. ucontext_t {.importc, header: "<ucontext.h>".} = object
  68. uc_link: ptr ucontext_t
  69. uc_stack: stack_t
  70. Context = ucontext_t
  71. proc getcontext(context: var ucontext_t): int32 {.importc,
  72. header: "<ucontext.h>".}
  73. proc setcontext(context: var ucontext_t): int32 {.importc,
  74. header: "<ucontext.h>".}
  75. proc swapcontext(fromCtx, toCtx: var ucontext_t): int32 {.importc,
  76. header: "<ucontext.h>".}
  77. proc makecontext(context: var ucontext_t, fn: pointer, argc: int32) {.importc,
  78. header: "<ucontext.h>", varargs.}
  79. elif coroBackend == CORO_BACKEND_SETJMP:
  80. proc coroExecWithStack*(fn: pointer, stack: pointer) {.noreturn,
  81. importc: "narch_$1", fastcall.}
  82. when defined(amd64):
  83. {.compile: "../arch/x86/amd64.S".}
  84. elif defined(i386):
  85. {.compile: "../arch/x86/i386.S".}
  86. else:
  87. # coroExecWithStack is defined in assembly. To support other platforms
  88. # please provide implementation of this procedure.
  89. {.error: "Unsupported architecture.".}
  90. when defined(nimCoroutinesSetjmpBundled):
  91. # Use setjmp/longjmp implementation shipped with compiler.
  92. when defined(amd64):
  93. type
  94. JmpBuf = array[0x50 + 0x10, uint8]
  95. elif defined(i386):
  96. type
  97. JmpBuf = array[0x1C, uint8]
  98. else:
  99. # Bundled setjmp/longjmp are defined in assembly. To support other
  100. # platforms please provide implementations of these procedures.
  101. {.error: "Unsupported architecture.".}
  102. proc setjmp(ctx: var JmpBuf): int {.importc: "narch_$1".}
  103. proc longjmp(ctx: JmpBuf, ret = 1) {.importc: "narch_$1".}
  104. else:
  105. # Use setjmp/longjmp implementation provided by the system.
  106. type
  107. JmpBuf {.importc: "jmp_buf", header: "<setjmp.h>".} = object
  108. proc setjmp(ctx: var JmpBuf): int {.importc, header: "<setjmp.h>".}
  109. proc longjmp(ctx: JmpBuf, ret = 1) {.importc, header: "<setjmp.h>".}
  110. type
  111. Context = JmpBuf
  112. when defined(unix):
  113. # GLibc fails with "*** longjmp causes uninitialized stack frame ***" because
  114. # our custom stacks are not initialized to a magic value.
  115. when defined(osx):
  116. # workaround: error: The deprecated ucontext routines require _XOPEN_SOURCE to be defined
  117. const extra = " -D_XOPEN_SOURCE"
  118. else:
  119. const extra = ""
  120. {.passc: "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0" & extra.}
  121. const
  122. CORO_CREATED = 0
  123. CORO_EXECUTING = 1
  124. CORO_FINISHED = 2
  125. type
  126. Stack {.pure.} = object
  127. top: pointer # Top of the stack. Pointer used for deallocating stack if we own it.
  128. bottom: pointer # Very bottom of the stack, acts as unique stack identifier.
  129. size: int
  130. Coroutine {.pure.} = object
  131. execContext: Context
  132. fn: proc()
  133. state: int
  134. lastRun: Ticks
  135. sleepTime: float
  136. stack: Stack
  137. reference: CoroutineRef
  138. CoroutinePtr = ptr Coroutine
  139. CoroutineRef* = ref object
  140. ## CoroutineRef holds a pointer to actual coroutine object. Public API always returns
  141. ## CoroutineRef instead of CoroutinePtr in order to allow holding a reference to coroutine
  142. ## object while it can be safely deallocated by coroutine scheduler loop. In this case
  143. ## Coroutine.reference.coro is set to nil. Public API checks for it being nil and
  144. ## gracefully fails if it is nil.
  145. coro: CoroutinePtr
  146. CoroutineLoopContext = ref object
  147. coroutines: DoublyLinkedList[CoroutinePtr]
  148. current: DoublyLinkedNode[CoroutinePtr]
  149. loop: Coroutine
  150. ncbottom: pointer # non coroutine stack botttom
  151. var ctx {.threadvar.}: CoroutineLoopContext
  152. proc getCurrent(): CoroutinePtr =
  153. ## Returns current executing coroutine object.
  154. var node = ctx.current
  155. if node != nil:
  156. return node.value
  157. return nil
  158. proc initialize() =
  159. ## Initializes coroutine state of current thread.
  160. if ctx == nil:
  161. ctx = CoroutineLoopContext()
  162. ctx.coroutines = initDoublyLinkedList[CoroutinePtr]()
  163. ctx.loop = Coroutine()
  164. ctx.loop.state = CORO_EXECUTING
  165. when not useOrcArc:
  166. ctx.ncbottom = GC_getActiveStack()
  167. when coroBackend == CORO_BACKEND_FIBERS:
  168. ctx.loop.execContext = ConvertThreadToFiberEx(nil, FIBER_FLAG_FLOAT_SWITCH)
  169. proc runCurrentTask()
  170. proc switchTo(current, to: CoroutinePtr) =
  171. ## Switches execution from `current` into `to` context.
  172. to.lastRun = getTicks()
  173. # Update position of current stack so gc invoked from another stack knows how much to scan.
  174. when not useOrcArc:
  175. GC_setActiveStack(current.stack.bottom)
  176. nimGC_setStackBottom(current.stack.bottom)
  177. var frame = getFrameState()
  178. block:
  179. # Execution will switch to another fiber now. We do not need to update current stack
  180. when coroBackend == CORO_BACKEND_FIBERS:
  181. SwitchToFiber(to.execContext)
  182. elif coroBackend == CORO_BACKEND_UCONTEXT:
  183. discard swapcontext(current.execContext, to.execContext)
  184. elif coroBackend == CORO_BACKEND_SETJMP:
  185. var res = setjmp(current.execContext)
  186. if res == 0:
  187. if to.state == CORO_EXECUTING:
  188. # Coroutine is resumed.
  189. longjmp(to.execContext, 1)
  190. elif to.state == CORO_CREATED:
  191. # Coroutine is started.
  192. coroExecWithStack(runCurrentTask, to.stack.bottom)
  193. #doAssert false
  194. else:
  195. {.error: "Invalid coroutine backend set.".}
  196. # Execution was just resumed. Restore frame information and set active stack.
  197. setFrameState(frame)
  198. when not useOrcArc:
  199. GC_setActiveStack(current.stack.bottom)
  200. nimGC_setStackBottom(ctx.ncbottom)
  201. proc suspend*(sleepTime: float = 0) =
  202. ## Stops coroutine execution and resumes no sooner than after `sleeptime` seconds.
  203. ## Until then other coroutines are executed.
  204. var current = getCurrent()
  205. current.sleepTime = sleepTime
  206. nimGC_setStackBottom(ctx.ncbottom)
  207. switchTo(current, addr(ctx.loop))
  208. proc runCurrentTask() =
  209. ## Starts execution of current coroutine and updates it's state through coroutine's life.
  210. var sp {.volatile.}: pointer
  211. sp = addr(sp)
  212. block:
  213. var current = getCurrent()
  214. current.stack.bottom = sp
  215. nimGC_setStackBottom(current.stack.bottom)
  216. # Execution of new fiber just started. Since it was entered not through `switchTo` we
  217. # have to set active stack here as well. GC_removeStack() has to be called in main loop
  218. # because we still need stack available in final suspend(0) call from which we will not
  219. # return.
  220. when not useOrcArc:
  221. GC_addStack(sp)
  222. # Activate current stack because we are executing in a new coroutine.
  223. GC_setActiveStack(sp)
  224. current.state = CORO_EXECUTING
  225. try:
  226. current.fn() # Start coroutine execution
  227. except:
  228. echo "Unhandled exception in coroutine."
  229. writeStackTrace()
  230. current.state = CORO_FINISHED
  231. nimGC_setStackBottom(ctx.ncbottom)
  232. suspend(0) # Exit coroutine without returning from coroExecWithStack()
  233. doAssert false
  234. proc start*(c: proc(), stacksize: int = defaultStackSize): CoroutineRef {.discardable.} =
  235. ## Schedule coroutine for execution. It does not run immediately.
  236. if ctx == nil:
  237. initialize()
  238. var coro: CoroutinePtr
  239. when coroBackend == CORO_BACKEND_FIBERS:
  240. coro = cast[CoroutinePtr](alloc0(sizeof(Coroutine)))
  241. coro.execContext = CreateFiberEx(stacksize, stacksize,
  242. FIBER_FLAG_FLOAT_SWITCH,
  243. (proc(p: pointer) {.stdcall.} = runCurrentTask()), nil)
  244. else:
  245. coro = cast[CoroutinePtr](alloc0(sizeof(Coroutine) + stacksize))
  246. coro.stack.top = cast[pointer](cast[ByteAddress](coro) + sizeof(Coroutine))
  247. coro.stack.bottom = cast[pointer](cast[ByteAddress](coro.stack.top) + stacksize)
  248. when coroBackend == CORO_BACKEND_UCONTEXT:
  249. discard getcontext(coro.execContext)
  250. coro.execContext.uc_stack.ss_sp = coro.stack.top
  251. coro.execContext.uc_stack.ss_size = stacksize
  252. coro.execContext.uc_link = addr(ctx.loop.execContext)
  253. makecontext(coro.execContext, runCurrentTask, 0)
  254. coro.fn = c
  255. coro.stack.size = stacksize
  256. coro.state = CORO_CREATED
  257. coro.reference = CoroutineRef(coro: coro)
  258. ctx.coroutines.append(coro)
  259. return coro.reference
  260. proc run*() =
  261. ## Starts main coroutine scheduler loop which exits when all coroutines exit.
  262. ## Calling this proc starts execution of first coroutine.
  263. initialize()
  264. ctx.current = ctx.coroutines.head
  265. var minDelay: float = 0
  266. while ctx.current != nil:
  267. var current = getCurrent()
  268. var remaining = current.sleepTime - (float(getTicks() - current.lastRun) / 1_000_000_000)
  269. if remaining <= 0:
  270. # Save main loop context. Suspending coroutine will resume after this statement with
  271. switchTo(addr(ctx.loop), current)
  272. else:
  273. if minDelay > 0 and remaining > 0:
  274. minDelay = min(remaining, minDelay)
  275. else:
  276. minDelay = remaining
  277. if current.state == CORO_FINISHED:
  278. var next = ctx.current.prev
  279. if next == nil:
  280. # If first coroutine ends then `prev` is nil even if more coroutines
  281. # are to be scheduled.
  282. next = ctx.current.next
  283. current.reference.coro = nil
  284. ctx.coroutines.remove(ctx.current)
  285. when not useOrcArc:
  286. GC_removeStack(current.stack.bottom)
  287. when coroBackend == CORO_BACKEND_FIBERS:
  288. DeleteFiber(current.execContext)
  289. else:
  290. dealloc(current.stack.top)
  291. dealloc(current)
  292. ctx.current = next
  293. elif ctx.current == nil or ctx.current.next == nil:
  294. ctx.current = ctx.coroutines.head
  295. os.sleep(int(minDelay * 1000))
  296. else:
  297. ctx.current = ctx.current.next
  298. proc alive*(c: CoroutineRef): bool = c.coro != nil and c.coro.state != CORO_FINISHED
  299. ## Returns `true` if coroutine has not returned, `false` otherwise.
  300. proc wait*(c: CoroutineRef, interval = 0.01) =
  301. ## Returns only after coroutine `c` has returned. `interval` is time in seconds how often.
  302. while alive(c):
  303. suspend(interval)