about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-01-04 01:18:59 +0100
committerbptato <nincsnevem662@gmail.com>2023-01-04 01:18:59 +0100
commit181ea25b5626c697cc9b6bd3e42c85ea920be0f3 (patch)
tree92338bdbf5743ac2e5c383b6abe13ec3518ed433 /src
parenteda263319d5f6b4f7392398a42d67f04056c23e1 (diff)
downloadchawan-181ea25b5626c697cc9b6bd3e42c85ea920be0f3.tar.gz
client, pager, dom, ...: better error handling
Now the browser shouldn't completely die when a buffer crashes.
Diffstat (limited to 'src')
-rw-r--r--src/display/client.nim158
-rw-r--r--src/display/pager.nim17
-rw-r--r--src/html/dom.nim12
-rw-r--r--src/html/env.nim4
-rw-r--r--src/ips/forkserver.nim26
5 files changed, 121 insertions, 96 deletions
diff --git a/src/display/client.nim b/src/display/client.nim
index bc316946..ab12e2f8 100644
--- a/src/display/client.nim
+++ b/src/display/client.nim
@@ -149,8 +149,8 @@ proc alert(client: Client, msg: string) {.jsfunc.} =
 
 proc handlePagerEvents(client: Client) =
   let container = client.pager.container
-  if container != nil and not client.pager.handleEvents(container):
-    client.quit(1)
+  if container != nil:
+    client.pager.handleEvents(container)
 
 proc input(client: Client) =
   restoreStdin(client.console.tty.getFileHandle())
@@ -245,6 +245,24 @@ proc clearInterval(client: Client, id: int) {.jsfunc.} =
 
 let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint
 
+proc log(console: Console, ss: varargs[string]) {.jsfunc.} =
+  for i in 0..<ss.len:
+    console.err.write(ss[i])
+    if i != ss.high:
+      console.err.write(' ')
+  console.err.write('\n')
+  console.err.flush()
+
+proc show(console: Console) {.jsfunc.} =
+  if console.pager.container != console.container:
+    console.prev = console.pager.container
+    console.pager.setContainer(console.container)
+    console.container.requestLines()
+
+proc hide(console: Console) {.jsfunc.} =
+  if console.pager.container == console.container:
+    console.pager.setContainer(console.prev)
+
 proc acceptBuffers(client: Client) =
   while client.pager.unreg.len > 0:
     let (pid, stream) = client.pager.unreg.pop()
@@ -266,27 +284,59 @@ proc acceptBuffers(client: Client) =
       let fd = stream.source.getFd()
       client.fdmap[int(fd)] = container
       client.selector.registerHandle(fd, {Read}, nil)
-      if not client.pager.handleEvents(container):
-        client.quit(1)
-      if container == client.pager.container:
-        client.pager.showAlerts()
-        client.pager.draw()
+      client.pager.handleEvents(container)
     else:
       #TODO uh what?
-      eprint "???"
+      client.console.log("???")
       stream.close()
 
-proc log(console: Console, ss: varargs[string]) {.jsfunc.} =
-  for i in 0..<ss.len:
-    console.err.write(ss[i])
-    if i != ss.high:
-      console.err.write(' ')
-  console.err.write('\n')
-  console.err.flush()
-
 proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize_t): cint {.
   importc: "setvbuf", header: "<stdio.h>", tags: [].}
 
+proc handleRead(client: Client, fd: int) =
+  if client.console.tty != nil and fd == client.console.tty.getFileHandle():
+    client.input()
+    client.handlePagerEvents()
+  elif fd == client.dispatcher.forkserver.estream.fd:
+    var nl = true
+    while true:
+      try:
+        let c = client.dispatcher.forkserver.estream.readChar()
+        if nl:
+          client.console.err.write("STDERR: ")
+          nl = false
+        client.console.err.write(c)
+        nl = c == '\n'
+      except IOError:
+        break
+    client.console.err.flush()
+  else:
+    let container = client.fdmap[fd]
+    client.pager.handleEvent(container)
+
+proc handleError(client: Client, fd: int) =
+  if client.console.tty != nil and fd == client.console.tty.getFileHandle():
+    #TODO do something here...
+    stderr.write("Error in tty\n")
+    quit(1)
+  elif fd == client.dispatcher.forkserver.estream.fd:
+    #TODO do something here...
+    stderr.write("Fork server crashed :(\n")
+    quit(1)
+  else:
+    if fd in client.fdmap:
+      let container = client.fdmap[fd]
+      if container != client.console.container:
+        client.console.log("Error in buffer", $container.location)
+      else:
+        client.console.container = nil
+      client.selector.unregister(fd)
+      client.fdmap.del(fd)
+    if client.console.container != nil:
+      client.console.show()
+    else:
+      quit(1)
+
 proc inputLoop(client: Client) =
   let selector = client.selector
   discard c_setvbuf(client.console.tty, nil, IONBF, 0)
@@ -296,22 +346,13 @@ proc inputLoop(client: Client) =
     let events = client.selector.select(-1)
     for event in events:
       if Read in event.events:
-        if event.fd == client.console.tty.getFileHandle():
-          client.input()
-          client.handlePagerEvents()
-        else:
-          let container = client.fdmap[event.fd]
-          if not client.pager.handleEvent(container):
-            client.quit(1)
+        client.handleRead(event.fd)
       if Error in event.events:
-        #TODO handle errors
-        client.alert("Error in selected fds, check console")
-        client.console.log($event)
+        client.handleError(event.fd)
       if Signal in event.events: 
-        if event.fd == sigwinch:
-          client.attrs = getWindowAttributes(client.console.tty)
-          client.pager.windowChange(client.attrs)
-        else: assert false
+        assert event.fd == sigwinch
+        client.attrs = getWindowAttributes(client.console.tty)
+        client.pager.windowChange(client.attrs)
       if Event.Timer in event.events:
         if event.fd in client.interval_fdis:
           client.intervals[client.interval_fdis[event.fd]].handler()
@@ -324,44 +365,18 @@ proc inputLoop(client: Client) =
       client.command(client.pager.scommand)
       client.pager.scommand = ""
       client.handlePagerEvents()
+    client.acceptBuffers()
     client.pager.showAlerts()
     client.pager.draw()
-    client.acceptBuffers()
-
-proc dumpLoop(client: Client) =
-  while client.pager.numload > 0:
-    let events = client.selector.select(-1)
-    for event in events:
-      if Read in event.events:
-        let container = client.fdmap[event.fd]
-        if not client.pager.handleEvent(container):
-          client.quit(1)
-      if Error in event.events:
-        #TODO handle errors
-        client.alert("Error in selected fds, check console")
-        client.console.log($event)
-      if Event.Timer in event.events:
-        if event.fd in client.interval_fdis:
-          client.intervals[client.interval_fdis[event.fd]].handler()
-        elif event.fd in client.timeout_fdis:
-          let id = client.timeout_fdis[event.fd]
-          let timeout = client.timeouts[id]
-          timeout.handler()
-          client.clearTimeout(id)
-    client.acceptBuffers()
 
 proc headlessLoop(client: Client) =
-  while client.timeouts.len + client.intervals.len != 0:
+  while client.timeouts.len + client.intervals.len != 0 or client.pager.numload > 0:
     let events = client.selector.select(-1)
     for event in events:
       if Read in event.events:
-        let container = client.fdmap[event.fd]
-        if not client.pager.handleEvent(container):
-          client.quit(1)
+        client.handleRead(event.fd)
       if Error in event.events:
-        #TODO handle errors
-        client.alert("Error in selected fds, check console")
-        client.console.log($event)
+        client.handleError(event.fd)
       if Event.Timer in event.events:
         if event.fd in client.interval_fdis:
           client.intervals[client.interval_fdis[event.fd]].handler()
@@ -404,11 +419,17 @@ proc newConsole(pager: Pager, tty: File): Console =
     result.err = newFileStream(stderr)
 
 proc dumpBuffers(client: Client) =
-  client.dumpLoop()
+  client.headlessLoop()
   let ostream = newFileStream(stdout)
   for container in client.pager.containers:
-    client.pager.drawBuffer(container, ostream)
-    discard client.pager.handleEvents(container)
+    try:
+      client.pager.drawBuffer(container, ostream)
+      client.pager.handleEvents(container)
+    except IOError:
+      client.console.log("Error in buffer", $container.location)
+      # check for errors
+      client.handleRead(client.dispatcher.forkserver.estream.fd)
+      quit(1)
   stdout.close()
 
 proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], dump: bool) =
@@ -424,6 +445,7 @@ proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], du
       dump = true
   client.ssock = initServerSocket(false)
   client.selector = newSelector[Container]()
+  client.selector.registerHandle(int(client.dispatcher.forkserver.estream.fd), {Read}, nil)
   client.pager.launchPager(tty)
   client.console = newConsole(client.pager, tty)
   client.alive = true
@@ -463,16 +485,6 @@ proc nimCollect(client: Client) {.jsfunc.} =
 proc jsCollect(client: Client) {.jsfunc.} =
   JS_RunGC(client.jsrt)
 
-proc show(console: Console) {.jsfunc.} =
-  if console.pager.container != console.container:
-    console.prev = console.pager.container
-    console.pager.setContainer(console.container)
-    console.container.requestLines()
-
-proc hide(console: Console) {.jsfunc.} =
-  if console.pager.container == console.container:
-    console.pager.setContainer(console.prev)
-
 proc sleep(client: Client, millis: int) {.jsfunc.} =
   sleep millis
 
diff --git a/src/display/pager.nim b/src/display/pager.nim
index 47845956..9bb65b3c 100644
--- a/src/display/pager.nim
+++ b/src/display/pager.nim
@@ -739,8 +739,7 @@ proc handleEvent0(pager: Pager, container: Container, event: ContainerEvent): bo
       pager.gotoURL(newRequest(container.retry.pop()), ctype = container.contenttype)
     else:
       pager.alert("Can't load " & $container.source.location & " (error code " & $container.code & ")")
-    if pager.container == nil:
-      return false
+    return false
   of SUCCESS:
     if container.replace != nil:
       let n = container.replace.children.find(container)
@@ -775,8 +774,7 @@ proc handleEvent0(pager: Pager, container: Container, event: ContainerEvent): bo
     else:
       pager.alert("Error: maximum redirection depth reached")
       pager.deleteContainer(container)
-      if pager.container == nil:
-        return false
+      return false
   of ANCHOR:
     var url2 = newURL(container.source.location)
     url2.hash(event.anchor)
@@ -810,19 +808,18 @@ proc handleEvent0(pager: Pager, container: Container, event: ContainerEvent): bo
   of NO_EVENT: discard
   return true
 
-proc handleEvents*(pager: Pager, container: Container): bool =
+proc handleEvents*(pager: Pager, container: Container) =
   while container.events.len > 0:
     let event = container.events.popFirst()
     if not pager.handleEvent0(container, event):
-      return false
-  return true
+      break
 
-proc handleEvent*(pager: Pager, container: Container): bool =
+proc handleEvent*(pager: Pager, container: Container) =
   try:
     container.handleEvent()
+    pager.handleEvents(container)
   except IOError:
-    return false
-  return pager.handleEvents(container)
+    discard
 
 proc addPagerModule*(ctx: JSContext) =
   ctx.registerType(Pager)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 6f98d9d4..228f48e1 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -97,7 +97,7 @@ type
   # this and the Client console.
   # TODO: merge those two
   console* = ref object
-    err: Stream
+    err*: Stream
 
   NamedNodeMap = ref object
     element: Element
@@ -1965,18 +1965,16 @@ proc fetchClassicScript(element: HTMLScriptElement, url: URL,
         let script = createClassicScript(source, url, options, false)
         element.markAsReady(ScriptResult(t: RESULT_SCRIPT, script: script))
 
-#TODO TODO TODO do something with this (redirect stderr?)
 proc log*(console: console, ss: varargs[string]) {.jsfunc.} =
   var s = ""
   for i in 0..<ss.len:
     s &= ss[i]
-    #console.err.write(ss[i])
+    console.err.write(ss[i])
     if i != ss.high:
       s &= ' '
-      #console.err.write(' ')
-  eprint s
-  #console.err.write('\n')
-  #console.err.flush()
+      console.err.write(' ')
+  console.err.write('\n')
+  console.err.flush()
 
 proc execute*(element: HTMLScriptElement) =
   let document = element.document
diff --git a/src/html/env.nim b/src/html/env.nim
index a232baa5..7f270e58 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -1,3 +1,5 @@
+import streams
+
 import html/dom
 import html/htmlparser
 import io/loader
@@ -51,7 +53,7 @@ proc addNavigatorModule(ctx: JSContext) =
 
 proc newWindow*(scripting: bool, loader = none(FileLoader)): Window =
   result = Window(
-    console: console(),
+    console: console(err: newFileStream(stderr)),
     navigator: Navigator(),
     loader: loader,
     settings: EnvironmentSettings(
diff --git a/src/ips/forkserver.nim b/src/ips/forkserver.nim
index 906d60a4..85b4aae5 100644
--- a/src/ips/forkserver.nim
+++ b/src/ips/forkserver.nim
@@ -25,6 +25,7 @@ type
     process*: Pid
     istream*: Stream
     ostream*: Stream
+    estream*: PosixStream
 
   ForkServerContext = object
     istream: Stream
@@ -97,10 +98,14 @@ proc forkBuffer(ctx: var ForkServerContext): Pid =
     )
   )
   let pid = fork()
+  #if pid == -1:
+  #  raise newException(Defect, "Failed to fork process.")
   if pid == 0:
     for i in 0 ..< ctx.children.len: ctx.children[i] = (Pid(0), Pid(0))
     ctx.children.setLen(0)
     zeroMem(addr ctx, sizeof(ctx))
+    discard close(stdin.getFileHandle())
+    discard close(stdout.getFileHandle())
     launchBuffer(config, source, attrs, loader, mainproc)
     assert false
   ctx.children.add((pid, loader.process))
@@ -152,34 +157,45 @@ proc runForkServer() =
 
 proc newForkServer*(): ForkServer =
   new(result)
-  var pipefd_in: array[2, cint]
-  var pipefd_out: array[2, cint]
+  var pipefd_in: array[2, cint] # stdin in forkserver
+  var pipefd_out: array[2, cint] # stdout in forkserver
+  var pipefd_err: array[2, cint] # stderr in forkserver
   if pipe(pipefd_in) == -1:
     raise newException(Defect, "Failed to open input pipe.")
   if pipe(pipefd_out) == -1:
     raise newException(Defect, "Failed to open output pipe.")
+  if pipe(pipefd_err) == -1:
+    raise newException(Defect, "Failed to open error pipe.")
   let pid = fork()
   if pid == -1:
     raise newException(Defect, "Failed to fork the fork process.")
   elif pid == 0:
     # child process
-    let readfd = pipefd_in[0]
     discard close(pipefd_in[1]) # close write
-    let writefd = pipefd_out[1]
     discard close(pipefd_out[0]) # close read
+    discard close(pipefd_err[0]) # close read
+    let readfd = pipefd_in[0]
+    let writefd = pipefd_out[1]
+    let errfd = pipefd_err[1]
     discard dup2(readfd, stdin.getFileHandle())
     discard dup2(writefd, stdout.getFileHandle())
+    discard dup2(errfd, stderr.getFileHandle())
+    stderr.flushFile()
     discard close(pipefd_in[0])
     discard close(pipefd_out[1])
+    discard close(pipefd_err[1])
     runForkServer()
     assert false
   else:
     discard close(pipefd_in[0]) # close read
     discard close(pipefd_out[1]) # close write
-    var readf, writef: File
+    discard close(pipefd_err[1]) # close write
+    var writef, readf: File
     if not open(writef, pipefd_in[1], fmWrite):
       raise newException(Defect, "Failed to open output handle")
     if not open(readf, pipefd_out[0], fmRead):
       raise newException(Defect, "Failed to open input handle")
     result.ostream = newFileStream(writef)
     result.istream = newFileStream(readf)
+    result.estream = newPosixStream(pipefd_err[0])
+    discard fcntl(pipefd_err[0], F_SETFL, fcntl(pipefd_err[0], F_GETFL, 0) or O_NONBLOCK)