diff options
author | bptato <nincsnevem662@gmail.com> | 2024-09-23 19:51:20 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-09-23 19:58:54 +0200 |
commit | fcd9aa9f9c604ed5d104343542962a26b2acda62 (patch) | |
tree | 7b0eea9b63bacc27cdc6471e2b2409b7b5d15c9e /src/local | |
parent | 8e8c7f0911f4a20446a83090d722fecaf203f6f3 (diff) | |
download | chawan-fcd9aa9f9c604ed5d104343542962a26b2acda62.tar.gz |
Replace std/selectors with poll
std/selectors uses OS-specific selector APIs, which sounds good in theory (faster than poll!), but sucks for portability in practice. Sure, you can fix portability bugs, but who knows how many there are on untested platforms... poll is standard, so if it works on one computer it should work on all other ones. (I hope.) As a bonus, I rewrote the timeout API for poll, which incidentally fixes setTimeout across forks. Also, SIGWINCH should now work on all platforms (as we self-pipe instead of signalfd/kqueue magic).
Diffstat (limited to 'src/local')
-rw-r--r-- | src/local/client.nim | 130 | ||||
-rw-r--r-- | src/local/pager.nim | 11 |
2 files changed, 78 insertions, 63 deletions
diff --git a/src/local/client.nim b/src/local/client.nim index 6c1d505c..f32e758d 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -4,7 +4,6 @@ import std/net import std/options import std/os import std/posix -import std/selectors import std/strutils import std/tables @@ -19,6 +18,7 @@ import html/formdata import html/xmlhttprequest import io/bufwriter import io/dynstream +import io/poll import io/promise import io/serversocket import js/console @@ -74,8 +74,8 @@ type func console(client: Client): Console {.jsfget.} = return client.consoleWrapper.console -template selector(client: Client): Selector[int] = - client.pager.selector +template pollData(client: Client): PollData = + client.pager.pollData template forkserver(client: Client): ForkServer = client.pager.forkserver @@ -144,6 +144,10 @@ proc evalJS(client: Client; src, filename: string; module = false): JSValue = proc evalJSFree(client: Client; src, filename: string) = JS_FreeValue(client.jsctx, client.evalJS(src, filename)) +proc evalJSFree2(opaque: RootRef; src, filename: string) = + let client = Client(opaque) + client.evalJSFree(src, filename) + proc command0(client: Client; src: string; filename = "<command>"; silence = false; module = false) = let ret = client.evalJS(src, filename, module = module) @@ -358,9 +362,6 @@ proc input(client: Client): EmptyPromise = p.resolve() return p -when ioselSupportedPlatform: - let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint - proc showConsole(client: Client) {.jsfunc.} = let container = client.consoleWrapper.container if client.pager.container != container: @@ -381,7 +382,7 @@ proc acceptBuffers(client: Client) = if container.iface != nil: # fully connected let stream = container.iface.stream let fd = int(stream.source.fd) - client.selector.unregister(fd) + client.pollData.unregister(fd) client.loader.unset(fd) stream.sclose() elif container.process != -1: # connecting to buffer process @@ -390,12 +391,12 @@ proc acceptBuffers(client: Client) = elif (let item = pager.findConnectingContainer(container); item != nil): # connecting to URL let stream = item.stream - client.selector.unregister(int(stream.fd)) + client.pollData.unregister(int(stream.fd)) stream.sclose() client.loader.unset(item) let registerFun = proc(fd: int) = - client.selector.unregister(fd) - client.selector.registerHandle(fd, {Read, Write}, 0) + client.pollData.unregister(fd) + client.pollData.register(fd, POLLIN or POLLOUT) for item in pager.procmap: let container = item.container let stream = connectSocketStream(client.config.external.sockdir, @@ -444,7 +445,7 @@ proc acceptBuffers(client: Client) = container.setCloneStream(stream, registerFun) let fd = int(stream.fd) client.loader.put(ContainerData(stream: stream, container: container)) - client.selector.registerHandle(fd, {Read}, 0) + client.pollData.register(fd, POLLIN) pager.handleEvents(container) pager.procmap.setLen(0) @@ -505,8 +506,8 @@ proc handleRead(client: Client; fd: int) = proc handleWrite(client: Client; fd: int) = let container = ContainerData(client.loader.get(fd)).container if container.iface.stream.flushWrite(): - client.selector.unregister(fd) - client.selector.registerHandle(fd, {Read}, 0) + client.pollData.unregister(fd) + client.pollData.register(fd, POLLIN) proc flushConsole*(client: Client) {.jsfunc.} = if client.console == nil: @@ -534,7 +535,7 @@ proc handleError(client: Client; fd: int) = client.console.error("Error in buffer", $container.url) else: client.consoleWrapper.container = nil - client.selector.unregister(fd) + client.pollData.unregister(fd) client.loader.unset(fd) doAssert client.consoleWrapper.container != nil client.showConsole() @@ -546,31 +547,48 @@ proc handleError(client: Client; fd: int) = doAssert client.consoleWrapper.container != nil client.showConsole() +let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint + +proc setupSigwinch(client: Client): PosixStream = + var pipefd {.noinit.}: array[2, cint] + doAssert pipe(pipefd) != -1 + let writer = newPosixStream(pipefd[1]) + writer.setBlocking(false) + var gwriter {.global.}: PosixStream = nil + gwriter = writer + onSignal SIGWINCH: + discard sig + try: + gwriter.sendDataLoop([0u8]) + except ErrorAgain: + discard + let reader = newPosixStream(pipefd[0]) + reader.setBlocking(false) + return reader + proc inputLoop(client: Client) = - let selector = client.selector - selector.registerHandle(int(client.pager.term.istream.fd), {Read}, 0) - when ioselSupportedPlatform: - let sigwinch = selector.registerSignal(int(SIGWINCH), 0) - var keys: array[64, ReadyKey] + client.pollData.register(client.pager.term.istream.fd, POLLIN) + let sigwinch = client.setupSigwinch() + client.pollData.register(sigwinch.fd, POLLIN) while true: - let count = client.selector.selectInto(-1, keys) - for event in keys.toOpenArray(0, count - 1): - if Read in event.events: - client.handleRead(event.fd) - if Write in event.events: - client.handleWrite(event.fd) - if Error in event.events: - client.handleError(event.fd) - when ioselSupportedPlatform: - if Signal in event.events: - assert event.fd == sigwinch + let timeout = client.timeouts.sortAndGetTimeout() + client.pollData.poll(timeout) + for event in client.pollData.events: + let efd = int(event.fd) + if (event.revents and POLLIN) != 0: + if event.fd == sigwinch.fd: + sigwinch.drain() client.pager.windowChange() - if selectors.Event.Timer in event.events: - let r = client.timeouts.runTimeoutFd(event.fd) - assert r - let container = client.consoleWrapper.container - if container != nil: - container.tailOnLoad = true + else: + client.handleRead(efd) + if (event.revents and POLLOUT) != 0: + client.handleWrite(efd) + if (event.revents and POLLERR) != 0 or (event.revents and POLLHUP) != 0: + client.handleError(efd) + if client.timeouts.run(): + let container = client.consoleWrapper.container + if container != nil: + container.tailOnLoad = true client.runJSJobs() client.loader.unregistered.setLen(0) client.acceptBuffers() @@ -604,19 +622,18 @@ func hasSelectFds(client: Client): bool = client.pager.procmap.len > 0 proc headlessLoop(client: Client) = - var keys: array[64, ReadyKey] while client.hasSelectFds(): - let count = client.selector.selectInto(-1, keys) - for event in keys.toOpenArray(0, count - 1): - if Read in event.events: - client.handleRead(event.fd) - if Write in event.events: - client.handleWrite(event.fd) - if Error in event.events: - client.handleError(event.fd) - if selectors.Event.Timer in event.events: - let r = client.timeouts.runTimeoutFd(event.fd) - assert r + let timeout = client.timeouts.sortAndGetTimeout() + client.pollData.poll(timeout) + for event in client.pollData.events: + let efd = int(event.fd) + if (event.revents and POLLIN) != 0: + client.handleRead(efd) + if (event.revents and POLLOUT) != 0: + client.handleWrite(efd) + if (event.revents and POLLERR) != 0 or (event.revents and POLLHUP) != 0: + client.handleError(efd) + discard client.timeouts.run() client.runJSJobs() client.loader.unregistered.setLen(0) client.acceptBuffers() @@ -721,26 +738,25 @@ proc launchClient*(client: Client; pages: seq[string]; dump = false else: dump = true - let selector = newSelector[int]() - let efd = int(client.forkserver.estream.fd) - selector.registerHandle(efd, {Read}, 0) + let pager = client.pager + pager.pollData.register(client.forkserver.estream.fd, POLLIN) client.loader.registerFun = proc(fd: int) = - selector.registerHandle(fd, {Read}, 0) + pager.pollData.register(fd, POLLIN) client.loader.unregisterFun = proc(fd: int) = - selector.unregister(fd) - client.pager.launchPager(istream, selector) + pager.pollData.unregister(fd) + pager.launchPager(istream) let clearFun = proc() = client.clearConsole() let showFun = proc() = client.showConsole() let hideFun = proc() = client.hideConsole() - client.consoleWrapper = client.pager.addConsole(interactive = istream != nil, + client.consoleWrapper = pager.addConsole(interactive = istream != nil, clearFun, showFun, hideFun) #TODO passing console.err here makes it impossible to change it later. maybe # better associate it with jsctx - client.timeouts = newTimeoutState(client.selector, client.jsctx, - client.console.err, proc(src, file: string) = client.evalJSFree(src, file)) + client.timeouts = newTimeoutState(client.jsctx, client.console.err, + evalJSFree2, client) client.pager.timeouts = client.timeouts addExitProc((proc() = client.cleanup())) if client.config.start.startup_script != "": diff --git a/src/local/pager.nim b/src/local/pager.nim index 35f6ab4c..58ab9d77 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -4,7 +4,6 @@ import std/options import std/os import std/osproc import std/posix -import std/selectors import std/sets import std/tables @@ -14,6 +13,7 @@ import config/config import config/mailcap import io/bufreader import io/dynstream +import io/poll import io/promise import io/stdio import io/tempfile @@ -135,21 +135,21 @@ type jsctx: JSContext lastAlert: string # last alert seen by the user lineData: LineData - lineedit*: LineEdit lineHist: array[LineMode, LineHistory] + lineedit*: LineEdit linemode: LineMode loader*: FileLoader luctx: LUContext navDirection {.jsget.}: NavDirection notnum*: bool # has a non-numeric character been input already? numload*: int # number of pages currently being loaded + pollData*: PollData precnum*: int32 # current number prefix (when vi-numeric-prefix is true) procmap*: seq[ProcMapItem] refreshAllowed: HashSet[string] regex: Opt[Regex] reverseSearch: bool scommand*: string - selector*: Selector[int] status: Surface term*: Terminal timeouts*: TimeoutState @@ -365,8 +365,7 @@ proc setLoader*(pager: Pager; loader: FileLoader) = ) loader.key = pager.addLoaderClient(pager.loader.clientPid, config) -proc launchPager*(pager: Pager; istream: PosixStream; selector: Selector[int]) = - pager.selector = selector +proc launchPager*(pager: Pager; istream: PosixStream) = case pager.term.start(istream) of tsrSuccess: discard of tsrDA1Fail: @@ -2045,7 +2044,7 @@ proc connected(pager: Pager; container: Container; response: Response) = pager.refreshStatusMsg() proc unregisterFd(pager: Pager; fd: int) = - pager.selector.unregister(fd) + pager.pollData.unregister(fd) pager.loader.unregistered.add(fd) proc handleRead*(pager: Pager; item: ConnectingContainer) = |