emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/racket-mode 1d108edab0 3/3: racket-repl-output: buffer/"ba


From: ELPA Syncer
Subject: [nongnu] elpa/racket-mode 1d108edab0 3/3: racket-repl-output: buffer/"batch" stdout/stderr
Date: Tue, 21 Nov 2023 13:00:43 -0500 (EST)

branch: elpa/racket-mode
commit 1d108edab0adb7b1014a7881b8352f5980092fea
Author: Greg Hendershott <git@greghendershott.com>
Commit: Greg Hendershott <git@greghendershott.com>

    racket-repl-output: buffer/"batch" stdout/stderr
    
    Performance can be bad at either of two extremes: Very many outputs,
    or very large outputs. This can arise with stdout or stderr outputs:
    e.g. (for ([_ huge-number]) (print something)) or (print huge-thing).
    Use a manager thread to mediate and buffer/batch.
    
    (Note that very long lines can still be slow in any Emacs buffer, but
    (a) that can be mitigated by so-long and IIUC Emacs 29.1, and (b)
    anyway that's not a problem we were contributing.)
---
 racket/repl-output.rkt  | 94 ++++++++++++++++++++++++++++++++++++++++++++++---
 racket/repl-session.rkt |  5 +++
 racket/repl.rkt         |  9 ++---
 3 files changed, 99 insertions(+), 9 deletions(-)

diff --git a/racket/repl-output.rkt b/racket/repl-output.rkt
index c7857cf23e..df50da60f1 100644
--- a/racket/repl-output.rkt
+++ b/racket/repl-output.rkt
@@ -4,6 +4,7 @@
 #lang racket/base
 
 (require racket/async-channel
+         racket/match
          "repl-session.rkt")
 
 (provide repl-output-channel
@@ -14,6 +15,7 @@
          repl-output-exit
          repl-output-value
          repl-output-value-special
+         make-repl-output-manager
          make-repl-output-port
          make-repl-error-port
          repl-error-port?)
@@ -37,9 +39,93 @@
 ;; A channel from which the command-server can sync.
 (define repl-output-channel (make-async-channel))
 
+;; This manager thread mediates between the `repl-output' function and
+;; the `repl-output-channel` async-channel. It seeks a warm bowl of
+;; porridge for the number and size of stdout and stderr outputs.
+;;
+;; - stdout/stderr items may be held awhile in case the next item is
+;; the same kind. A run of consecutive items within a time span are
+;; consolidated into one.
+;;
+;; - On the other hand a very large stdout/stderr item is split into
+;; multiple smaller ones.
+;;
+;; So this is a kind of buffering or "batching", but using a timer
+;; instead of needing explicit flushing. At the same time, any
+;; non-stdout/stderr kind of output will automatically "flush",
+;; including items like 'prompt or 'run, so this works out fine as
+;; well.
+(struct repl-output-item (kind value))
+(define ((repl-output-manager-thread session-id))
+  (define msec-threshold 500)
+  (define size-threshold 4096)
+
+  (define (put* kind value)
+    (async-channel-put repl-output-channel
+                       (list 'repl-output session-id kind value)))
+  (define (put item)
+    (match item
+      [(repl-output-item (and kind (or (== 'stdout) (== 'stderr))) bstr)
+       (define len (bytes-length bstr))
+       (for ([beg (in-range 0 len size-threshold)])
+         (put* kind (subbytes bstr beg (min len (+ beg size-threshold)))))]
+      [(repl-output-item kind value)
+       (put* kind value)]))
+
+  (define pending-item #f)
+  (define pending-flush-alarm-evt never-evt)
+
+  (define (queue item)
+    (match-define (repl-output-item kind value) item)
+    (match pending-item
+      ;; No pending item. When the new item is stdout or stderr, and
+      ;; doesn't already exceed the size-threshold, set it as the
+      ;; pending item and start our countdown.
+      [#f
+       #:when (and (memq kind '(stdout stderr))
+                   (< (bytes-length value) size-threshold))
+       (set! pending-item item)
+       (set! pending-flush-alarm-evt
+             (alarm-evt (+ (current-inexact-milliseconds)
+                           msec-threshold)))]
+      ;; No pending item. Just send new item now.
+      [#f
+       (put item)]
+      ;; There's a pending item. New item is same kind. When appending
+      ;; their values is under the size-threshold, combine them.
+      [(repl-output-item (== kind) pending-value)
+       #:when (< (+ (bytes-length pending-value) (bytes-length value))
+                 size-threshold)
+       (set! pending-item
+             (repl-output-item kind
+                               (bytes-append pending-value value)))]
+      ;; There's a pending item. Send it then the new item, now.
+      [(? repl-output-item?)
+       (flush-pending)
+       (put item)]))
+
+  (define (flush-pending)
+    (when pending-item
+      (put pending-item)
+      (set! pending-item #f))
+    (set! pending-flush-alarm-evt never-evt))
+
+  (let loop ()
+    (sync (handle-evt (thread-receive-evt)
+                      (λ (_evt) (queue (thread-receive))))
+          (handle-evt pending-flush-alarm-evt
+                      (λ (_evt) (flush-pending))))
+    (loop)))
+
+(define (make-repl-output-manager session-id)
+  (thread (repl-output-manager-thread session-id)))
+
 (define (repl-output kind value)
-  (async-channel-put repl-output-channel
-                     (list 'repl-output (current-session-id) kind value)))
+  (define t (current-repl-output-manager))
+  (when t
+    (thread-send t
+                 (repl-output-item kind value)
+                 void)))
 
 ;; Various wrappers around repl-output:
 
@@ -89,10 +175,8 @@
 
 (define (make-repl-port kind)
   (define name (format "racket-mode-repl-~a" kind))
-  (define special-kind (string->symbol (format "~a-special" kind)))
   (define (write-out bstr start end non-block? breakable?)
-    (async-channel-put repl-output-channel
-                       (repl-output kind (subbytes bstr start end)))
+    (repl-output kind (subbytes bstr start end))
     (- end start))
   (define close void)
   (make-output-port name
diff --git a/racket/repl-session.rkt b/racket/repl-session.rkt
index 245466d6de..014be91f72 100644
--- a/racket/repl-session.rkt
+++ b/racket/repl-session.rkt
@@ -12,6 +12,7 @@
          current-repl-msg-chan
          current-submissions
          current-session-maybe-mod
+         current-repl-output-manager
          (struct-out session)
          get-session
          set-session!
@@ -24,6 +25,7 @@
 
 (struct session
   (thread           ;thread? the repl manager thread
+   repl-out-mgr     ;thread? the repl output manager thread
    repl-msg-chan    ;channel?
    submissions      ;channel?
    maybe-mod        ;(or/c #f module-path?)
@@ -35,6 +37,7 @@
 
 (define (set-session! sid maybe-mod)
   (hash-set! sessions sid (session (current-thread)
+                                   (current-repl-output-manager)
                                    (current-repl-msg-chan)
                                    (current-submissions)
                                    maybe-mod
@@ -49,6 +52,7 @@
 (define current-repl-msg-chan (make-parameter #f))
 (define current-submissions (make-parameter #f))
 (define current-session-maybe-mod (make-parameter #f))
+(define current-repl-output-manager (make-parameter #f))
 
 ;; A way to parameterize e.g. commands that need to work with a
 ;; specific REPL session. Called from e.g. a command-server thread.
@@ -57,6 +61,7 @@
     [(? session? s)
      (log-racket-mode-debug @~a{@~v[@car[args]]: using session ID @~v[sid]})
      (parameterize ([current-session-id          sid]
+                    [current-repl-output-manager (session-repl-out-mgr s)]
                     [current-repl-msg-chan       (session-repl-msg-chan s)]
                     [current-submissions         (session-submissions s)]
                     [current-session-maybe-mod   (session-maybe-mod s)]
diff --git a/racket/repl.rkt b/racket/repl.rkt
index abae84e343..7c96411334 100644
--- a/racket/repl.rkt
+++ b/racket/repl.rkt
@@ -167,10 +167,11 @@
   (log-racket-mode-info "starting repl session ~v" session-id)
   ;; Make pipe for user program input (as distinct form repl-submit
   ;; input).
-  (parameterize* ([current-session-id    session-id]
-                  [current-repl-msg-chan (make-channel)]
-                  [current-submissions   (make-channel)]
-                  [error-display-handler racket-mode-error-display-handler])
+  (parameterize* ([current-session-id          session-id]
+                  [current-repl-output-manager (make-repl-output-manager 
session-id)]
+                  [current-repl-msg-chan       (make-channel)]
+                  [current-submissions         (make-channel)]
+                  [error-display-handler       
racket-mode-error-display-handler])
     (set-session! session-id #f)
     (do-run
      (initial-run-config



reply via email to

[Prev in Thread] Current Thread [Next in Thread]