about summary refs log tree commit diff stats
path: root/src/display
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-11-24 20:03:21 +0100
committerbptato <nincsnevem662@gmail.com>2022-11-24 20:03:21 +0100
commit896489a6c500e28f13d0237ab691622cb5c5114f (patch)
tree91b92da01bc126c2489a3dd083df5f9de06927c6 /src/display
parentee930b0f5a587768d340c4204cf1f2e9fb818c89 (diff)
downloadchawan-896489a6c500e28f13d0237ab691622cb5c5114f.tar.gz
Avoid forking child processes from the main process
Caveat: this breaks piped streams.
Diffstat (limited to 'src/display')
-rw-r--r--src/display/client.nim90
-rw-r--r--src/display/pager.nim34
2 files changed, 79 insertions, 45 deletions
diff --git a/src/display/client.nim b/src/display/client.nim
index 9ef501ab..bd7f3977 100644
--- a/src/display/client.nim
+++ b/src/display/client.nim
@@ -1,3 +1,5 @@
+import nativesockets
+import net
 import options
 import os
 import selectors
@@ -22,14 +24,20 @@ import io/loader
 import io/request
 import io/term
 import io/window
+import ips/forkserver
+import ips/serialize
+import ips/serversocket
+import ips/socketstream
 import js/javascript
 import types/cookie
+import types/dispatcher
 import types/url
 
 type
   Client* = ref ClientObj
   ClientObj* = object
     attrs: WindowAttributes
+    dispatcher: Dispatcher
     feednext: bool
     s: string
     errormessage: string
@@ -46,6 +54,9 @@ type
     intervals: Table[int, tuple[handler: (proc()), fdi: int, tofree: JSValue]]
     timeout_fdis: Table[int, int]
     interval_fdis: Table[int, int]
+    fdmap: Table[int, Container]
+    ssock: ServerSocket
+    selector: Selector[Container]
 
   Console = ref object
     err: Stream
@@ -110,8 +121,6 @@ proc command(client: Client, src: string) =
 
 proc quit(client: Client, code = 0) {.jsfunc.} =
   client.pager.quit()
-  when defined(posix):
-    assert kill(client.loader.process, cint(SIGTERM)) == 0
   quit(code)
 
 proc feedNext(client: Client) {.jsfunc.} =
@@ -152,7 +161,7 @@ proc input(client: Client) =
 proc setTimeout[T: JSObject|string](client: Client, handler: T, timeout = 0): int {.jsfunc.} =
   let id = client.timeoutid
   inc client.timeoutid
-  let fdi = client.pager.selector.registerTimer(timeout, true, nil)
+  let fdi = client.selector.registerTimer(timeout, true, nil)
   client.timeout_fdis[fdi] = id
   when T is string:
     client.timeouts[id] = ((proc() =
@@ -172,7 +181,7 @@ proc setTimeout[T: JSObject|string](client: Client, handler: T, timeout = 0): in
 proc setInterval[T: JSObject|string](client: Client, handler: T, interval = 0): int {.jsfunc.} =
   let id = client.timeoutid
   inc client.timeoutid
-  let fdi = client.pager.selector.registerTimer(interval, false, nil)
+  let fdi = client.selector.registerTimer(interval, false, nil)
   client.interval_fdis[fdi] = id
   when T is string:
     client.intervals[id] = ((proc() =
@@ -192,26 +201,46 @@ proc setInterval[T: JSObject|string](client: Client, handler: T, interval = 0):
 proc clearTimeout(client: Client, id: int) {.jsfunc.} =
   if id in client.timeouts:
     let timeout = client.timeouts[id]
-    client.pager.selector.unregister(timeout.fdi)
+    client.selector.unregister(timeout.fdi)
     client.timeout_fdis.del(timeout.fdi)
     client.timeouts.del(id)
 
 proc clearInterval(client: Client, id: int) {.jsfunc.} =
   if id in client.intervals:
     let interval = client.intervals[id]
-    client.pager.selector.unregister(interval.fdi)
+    client.selector.unregister(interval.fdi)
     JS_FreeValue(client.jsctx, interval.tofree)
     client.interval_fdis.del(interval.fdi)
     client.intervals.del(id)
 
 let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint
 
+proc acceptBuffers(client: Client) =
+  var i = 0
+  while i < client.pager.procmap.len:
+    let stream = client.ssock.acceptSocketStream()
+    var pid: Pid
+    stream.sread(pid)
+    if pid in client.pager.procmap:
+      let container = client.pager.procmap[pid]
+      client.pager.procmap.del(pid)
+      container.istream = stream
+      container.ostream = stream
+      let fd = stream.source.getFd()
+      client.fdmap[int(fd)] = container
+      client.selector.registerHandle(fd, {Read}, nil)
+      inc i
+    else:
+      #TODO print an error?
+      stream.close()
+
 proc inputLoop(client: Client) =
-  let selector = client.pager.selector
+  let selector = client.selector
   selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, nil)
   let sigwinch = selector.registerSignal(int(SIGWINCH), nil)
   while true:
-    let events = client.pager.selector.select(-1)
+    client.acceptBuffers()
+    let events = client.selector.select(-1)
     for event in events:
       if event.fd == client.console.tty.getFileHandle():
         client.input()
@@ -227,7 +256,7 @@ proc inputLoop(client: Client) =
         client.attrs = getWindowAttributes(client.console.tty)
         client.pager.windowChange(client.attrs)
       else:
-        let container = client.pager.fdmap[FileHandle(event.fd)]
+        let container = client.fdmap[event.fd]
         if not client.pager.handleEvent(container):
           disableRawMode()
           for msg in client.pager.status:
@@ -257,7 +286,6 @@ proc newConsole(pager: Pager, tty: File): Console =
       raise newException(Defect, "Failed to open console pipe.")
     let url = newURL("javascript:console.show()")
     result.container = pager.readPipe0(some("text/plain"), pipefd[0], option(url))
-    pager.registerContainer(result.container)
     discard close(pipefd[0])
     var f: File
     if not open(f, pipefd[1], fmWrite):
@@ -265,9 +293,22 @@ proc newConsole(pager: Pager, tty: File): Console =
     result.err = newFileStream(f)
     result.pager = pager
     result.tty = tty
+    pager.registerContainer(result.container)
   else:
     result.err = newFileStream(stderr)
 
+proc dumpBuffers(client: Client) =
+  client.acceptBuffers()
+  for container in client.pager.containers:
+    container.load()
+  for msg in client.pager.status:
+    eprint msg
+  let ostream = newFileStream(stdout)
+  for container in client.pager.containers:
+    container.reshape(true)
+    client.pager.drawBuffer(container, ostream)
+  stdout.close()
+
 proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], dump: bool) =
   var tty: File
   var dump = dump
@@ -278,12 +319,11 @@ proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], du
       discard open(tty, "/dev/tty", fmRead)
     else:
       dump = true
+  client.ssock = initServerSocket(false)
+  client.selector = newSelector[Container]()
   client.pager.launchPager(tty)
   client.console = newConsole(client.pager, tty)
-  let pid = getpid()
-  addExitProc((proc() =
-    if pid == getpid():
-      client.quit()))
+  addExitProc((proc() = client.quit()))
   if client.config.startup != "":
     let s = if fileExists(client.config.startup):
       readFile(client.config.startup)
@@ -303,13 +343,7 @@ proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], du
   if not dump:
     client.inputLoop()
   else:
-    for msg in client.pager.status:
-      eprint msg
-    let ostream = newFileStream(stdout)
-    for container in client.pager.containers:
-      container.reshape(true)
-      client.pager.drawBuffer(container, ostream)
-    stdout.close()
+    client.dumpBuffers()
   client.quit()
 
 proc nimGCStats(client: Client): string {.jsfunc.} =
@@ -338,16 +372,16 @@ proc hide(console: Console) {.jsfunc.} =
 proc sleep(client: Client, millis: int) {.jsfunc.} =
   sleep millis
 
-proc newClient*(config: Config): Client =
+proc newClient*(config: Config, dispatcher: Dispatcher): Client =
   new(result)
   result.config = config
+  result.dispatcher = dispatcher
   result.attrs = getWindowAttributes(stdout)
-  result.loader = newFileLoader()
-  result.pager = newPager(config, result.attrs)
-  let rt = newJSRuntime()
-  rt.setInterruptHandler(interruptHandler, cast[pointer](result))
-  let ctx = rt.newJSContext()
-  result.jsrt = rt
+  result.loader = dispatcher.forkserver.newFileLoader()
+  result.pager = newPager(config, result.attrs, dispatcher)
+  result.jsrt = newJSRuntime()
+  result.jsrt.setInterruptHandler(interruptHandler, cast[pointer](result))
+  let ctx = result.jsrt.newJSContext()
   result.jsctx = ctx
   var global = ctx.getGlobalObject()
   ctx.registerType(Client, asglobal = true)
diff --git a/src/display/pager.nim b/src/display/pager.nim
index 62137805..3fdcd504 100644
--- a/src/display/pager.nim
+++ b/src/display/pager.nim
@@ -1,10 +1,12 @@
 import options
 import os
-import selectors
 import streams
 import tables
 import unicode
 
+when defined(posix):
+  import posix
+
 import buffer/buffer
 import buffer/cell
 import buffer/container
@@ -13,9 +15,12 @@ import io/lineedit
 import io/request
 import io/term
 import io/window
+import ips/forkserver
 import js/javascript
 import js/regex
+import types/buffersource
 import types/color
+import types/dispatcher
 import types/url
 import utils/twtstr
 
@@ -28,6 +33,7 @@ type
     attrs: WindowAttributes
     commandMode*: bool
     container*: Container
+    dispatcher*: Dispatcher
     lineedit*: Option[LineEdit]
     linemode*: LineMode
     username: string
@@ -39,8 +45,7 @@ type
     status*: seq[string]
     statusmsg*: FixedGrid
     tty: File
-    selector*: Selector[Container]
-    fdmap*: Table[FileHandle, Container]
+    procmap*: Table[Pid, Container]
     icpos: CursorPosition
     display: FixedGrid
     redraw*: bool
@@ -139,11 +144,11 @@ proc isearchBackward(pager: Pager) {.jsfunc.} =
 
 func attrs*(pager: Pager): WindowAttributes = pager.term.attrs
 
-proc newPager*(config: Config, attrs: WindowAttributes): Pager =
+proc newPager*(config: Config, attrs: WindowAttributes, dispatcher: Dispatcher): Pager =
   let pager = Pager(
+    dispatcher: dispatcher,
     config: config,
     attrs: attrs,
-    selector: newSelector[Container](),
     display: newFixedGrid(attrs.width, attrs.height - 1),
     statusmsg: newFixedGrid(attrs.width),
     term: newTerminal(stdout, config)
@@ -308,19 +313,17 @@ proc draw*(pager: Pager) =
   pager.container.redraw = false
 
 proc registerContainer*(pager: Pager, container: Container) =
-  pager.fdmap[container.ifd] = container
-  pager.selector.registerHandle(int(container.ifd), {Read}, pager.container)
+  pager.procmap[container.process] = container
 
 proc addContainer*(pager: Pager, container: Container) =
   container.parent = pager.container
   if pager.container != nil:
     pager.container.children.add(container)
-  pager.setContainer(container)
-  assert int(container.ifd) != 0
   pager.registerContainer(container)
+  pager.setContainer(container)
 
 proc dupeContainer(pager: Pager, container: Container, location: Option[URL]): Container =
-  return container.dupeBuffer(pager.config, location)
+  return pager.dispatcher.dupeBuffer(container, pager.config, location)
 
 proc dupeBuffer*(pager: Pager, location = none(URL)) {.jsfunc.} =
   pager.addContainer(pager.dupeContainer(pager.container, location))
@@ -396,10 +399,9 @@ proc deleteContainer(pager: Pager, container: Container) =
       pager.setContainer(nil)
   container.parent = nil
   container.children.setLen(0)
-  pager.fdmap.del(container.ifd)
-  pager.selector.unregister(int(container.ifd))
   container.istream.close()
   container.ostream.close()
+  pager.dispatcher.forkserver.removeChild(container.process)
 
 proc discardBuffer*(pager: Pager) {.jsfunc.} =
   if pager.container == nil or pager.container.parent == nil and
@@ -416,7 +418,7 @@ proc toggleSource*(pager: Pager) {.jsfunc.} =
       some("text/plain")
     else:
       some("text/html")
-    let container = pager.container.dupeBuffer(pager.config, contenttype = contenttype)
+    let container = pager.dispatcher.dupeBuffer(pager.container, pager.config, contenttype = contenttype)
     container.sourcepair = pager.container
     pager.container.sourcepair = container
     pager.addContainer(container)
@@ -445,10 +447,9 @@ proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), ctype = none(
       contenttype: ctype,
       location: request.url
     )
-    let container = newBuffer(pager.config, source)
+    let container = pager.dispatcher.newBuffer(pager.config, source)
     container.replace = replace
     pager.addContainer(container)
-    container.load()
   else:
     pager.container.redirect = some(request.url)
     pager.container.gotoAnchor(request.url.anchor)
@@ -496,8 +497,7 @@ proc readPipe0*(pager: Pager, ctype: Option[string], fd: FileHandle, location: O
     contenttype: some(ctype.get("text/plain")),
     location: location.get(newURL("file://-"))
   )
-  let container = newBuffer(pager.config, source, ispipe = true)
-  container.load()
+  let container = pager.dispatcher.newBuffer(pager.config, source, ispipe = true)
   return container
 
 proc readPipe*(pager: Pager, ctype: Option[string], fd: FileHandle) =