diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a63eff..76397e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,5 +26,5 @@ jobs: nimversion: ${{ matrix.nimversion }} - name: Test run: | - nimble test + nimble -y test nimble refresh diff --git a/.gitignore b/.gitignore index f1d1222..83233aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,18 @@ -/tests/* -!/tests/*.nim -!/tests/*.cfg +* +!**/ +!*.* +*.out +.DS_Store +!.gitattributes +!.gitignore +!readme.md +!.gitkeep +!*.nim +!*.nims +!*.nimble +!*.h +!*.css +!*.html +*.exe +!*/ +nimbledeps diff --git a/src/httpbeast.nim b/src/httpbeast.nim index 5b4c9d4..af1dc26 100644 --- a/src/httpbeast.nim +++ b/src/httpbeast.nim @@ -2,6 +2,8 @@ import selectors, net, nativesockets, os, httpcore, asyncdispatch, strutils, pos import parseutils import options, sugar, logging import macros +import std/exitprocs +import system / ansi_c from posix import ENOPROTOOPT @@ -51,6 +53,8 @@ type OnRequest* = proc (req: Request): Future[void] {.gcsafe.} + Startup = proc () {.closure, gcsafe.} + Settings* = object port*: Port bindAddr*: string @@ -68,15 +72,23 @@ type ## 4096 for Linux/AMD64 and 128 for others. ## * listen(2) Linux manual page: https://www.man7.org/linux/man-pages/man2/listen.2.html ## * SYN packet handling int the wild: https://blog.cloudflare.com/syn-packet-handling-in-the-wild/ + startup: Startup + ## An optional callback can be provided to execute at the beginning of + ## the `eventLoop` function for initializing thread variables or performing other related tasks. HttpBeastDefect* = ref object of Defect const serverInfo = "HttpBeast" +proc doNothing(): Startup {.gcsafe.} = + result = proc () {.closure, gcsafe.} = + discard + proc initSettings*(port: Port = Port(8080), bindAddr: string = "", numThreads: int = 0, + startup: Startup = doNothing(), domain = Domain.AF_INET, reusePort = true, listenBacklog = SOMAXCONN): Settings = @@ -88,6 +100,7 @@ proc initSettings*(port: Port = Port(8080), loggers: getHandlers(), reusePort: reusePort, listenBacklog: listenBacklog, + startup: startup ) proc initData(fdKind: FdKind, ip = ""): Data = @@ -333,6 +346,9 @@ proc eventLoop( ) = let (onRequest, settings, isMainThread) = params + if settings.startup != nil: + settings.startup() + if not isMainThread: # We are on a new thread. Re-add the loggers from the main thread. for logger in settings.loggers: @@ -544,6 +560,17 @@ proc run*(onRequest: OnRequest, settings: Settings) = createThread[(OnRequest, Settings, bool)]( t, eventLoop, (onRequest, settings, false) ) + when NimMajor >= 2: + addExitProc(proc() = + for thr in threads: + when compiles(pthread_cancel(thr.sys)): + discard pthread_cancel(thr.sys) + if not isNil(thr.core): + when defined(gcDestructors): + c_free(thr.core) + else: + deallocShared(thr.core) + ) else: assert false echo("Listening on port ", settings.port) # This line is used in the tester to signal readiness. diff --git a/tests/startup.ini b/tests/startup.ini new file mode 100644 index 0000000..5de2e31 --- /dev/null +++ b/tests/startup.ini @@ -0,0 +1,7 @@ +charset = "utf-8" +[Package] +name = "hello" +--threads:on +[Author] +name = "nim-lang" +website = "nim-lang.org" diff --git a/tests/startup.nim b/tests/startup.nim new file mode 100644 index 0000000..83a5ba7 --- /dev/null +++ b/tests/startup.nim @@ -0,0 +1,41 @@ +import os, options, asyncdispatch, parsecfg, strutils, streams +import httpbeast + +const CurDir = currentSourcePath.parentDir + +var threadsOn {.threadvar.}: bool +var name {.threadvar.}: string + +proc onRequest(req: Request): Future[void] = + if req.httpMethod == some(HttpGet): + case req.path.get() + of "/": + req.send("name:$#,threads:$#." % [name, $threadsOn]) + else: + req.send(Http404) + +var startup = proc () = + let configFile = CurDir / "startup.ini" + var f = newFileStream(configFile, fmRead) + assert f != nil, "cannot open " & configFile + var p: CfgParser + var section: string + open(p, f, configFile) + while true: + var e = next(p) + case e.kind + of cfgEof: break + of cfgSectionStart: ## a `[section]` has been parsed + section = e.section + of cfgKeyValuePair: + if section == "Package" and e.key == "name": + name = e.value + of cfgOption: + if e.key == "threads": + if e.value == "on": + threadsOn = true + of cfgError: + echo e.msg + close(p) + +run(onRequest, initSettings(startup = startup))