261029.1 · b95235bbfa19defc68c21353ce4014ec9e9976df
A tree page's buffer guess stays near its size, and a helper's crash shows
The builder grew 512 bytes a line before the parse, so a 1 MiB file of blank lines took 512 MB, a file with no grammar paid it too, and a tree of 16k lines overran the cache it was charged to. The guess now waits for the first node and keeps to 20 bytes a byte of source, and a buffer well past its rows is copied down before the cache counts it.
A helper that dies or times out mid-line now reports its own message or the timeout, not the cut line's field count.
reading declarations
Changes to syntax.go. All files
@@ -82,9 +82,18 @@ func parseSyntax(ctx context.Context, name string, src []byte, row func(synNode) return err } if err := readNodes(out, row); err != nil {+ // A helper that crashed or ran out of time leaves a cut line, so+ // its own message or the timeout says more than the cut does.+ timedOut := ctx.Err() != nil // Cancelling kills the helper, so Wait returns. cancel() cmd.Wait()+ if msg := strings.TrimSpace(stderr.String()); msg != "" {+ return errors.New(msg)+ }+ if timedOut {+ return context.DeadlineExceeded+ } return err } if err := cmd.Wait(); err != nil {@@ -192,8 +201,14 @@ func syntaxPage(ctx context.Context, name, href string, src []byte) (string, err } h := rows.done() // The rows keep the builder's whole buffer, which the guess in- // newSynRows may make larger than h.- syntaxCache.put(key, h, int64(rows.b.Cap()))+ // newSynRows may make larger than h. Well past it, h keeps a copy of+ // its own, so the cache holds no more than it counts.+ size := rows.b.Cap()+ if size > len(h)+len(h)/4 {+ h = strings.Clone(h)+ size = len(h)+ }+ syntaxCache.put(key, h, int64(size)) return h, nil } @@ -203,6 +218,7 @@ type synRows struct { b strings.Builder lines [][]byte href string+ size int // bytes of source open int // folds left open held synNode any bool // whether a node is held@@ -210,10 +226,7 @@ type synRows struct { // newSynRows starts the rows of src's tree, with line links to href. func newSynRows(href string, src []byte) *synRows {- w := &synRows{lines: bytes.Split(src, []byte("\n")), href: href}- // A row is about 120 bytes, and a line of a large file gives about- // four, so the builder seldom grows where it would cost the most.- w.b.Grow(len(w.lines) * 512)+ w := &synRows{lines: bytes.Split(src, []byte("\n")), href: href, size: len(src)} w.b.WriteString("<div class=\"syntax\">\n") return w }@@ -221,6 +234,13 @@ func newSynRows(href string, src []byte) *synRows { func (w *synRows) add(n synNode) { if w.any { w.row(w.held, n.depth > w.held.depth)+ } else {+ // A row is about 120 bytes, and a line of a large file gives about+ // four, so the builder seldom grows where it would cost the most.+ // A file of short or blank lines gives fewer, and its rows come to+ // about 17 bytes a byte of it, so the guess keeps to 20. It waits+ // for the first node, so a file with no tree costs nothing.+ w.b.Grow(min(len(w.lines)*512, w.size*20)) } w.held, w.any = n, true }