aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/transcription-config.el27
-rw-r--r--tests/test-transcription-process-and-sentinel.el50
2 files changed, 69 insertions, 8 deletions
diff --git a/modules/transcription-config.el b/modules/transcription-config.el
index e00306d1e..944063b88 100644
--- a/modules/transcription-config.el
+++ b/modules/transcription-config.el
@@ -195,6 +195,8 @@ transcript lands alongside the source, not next to the temp /tmp audio."
(txt-file (car outputs))
(log-file (cdr outputs))
(buffer-name (format " *transcribe-%s*" (file-name-nondirectory audio-file)))
+ (stderr-buffer-name (format " *transcribe-stderr-%s*"
+ (file-name-nondirectory audio-file)))
(process-name (format "transcribe-%s" (file-name-nondirectory audio-file))))
(unless (file-executable-p script)
@@ -203,15 +205,25 @@ transcript lands alongside the source, not next to the temp /tmp audio."
(cj/--init-log-file log-file audio-file script)
(let* ((process-environment (cj/--build-process-environment cj/transcribe-backend))
+ ;; A live, explicitly-managed buffer for stderr. Passing a file PATH
+ ;; to :stderr makes Emacs create a phantom buffer named after the
+ ;; path, so the error text never reaches the log file and that buffer
+ ;; leaks per run; the sentinel drains this buffer into the log and
+ ;; kills it. Keeping stderr off the stdout :buffer leaves the
+ ;; transcript (stdout) clean.
+ (stderr-buffer (with-current-buffer (get-buffer-create stderr-buffer-name)
+ (erase-buffer)
+ (current-buffer)))
(process (make-process
:name process-name
:buffer (get-buffer-create buffer-name)
:command (list script audio-file)
:sentinel (lambda (proc event)
- (cj/--transcription-sentinel proc event audio-file txt-file log-file)
+ (cj/--transcription-sentinel proc event audio-file
+ txt-file log-file stderr-buffer)
(when cleanup-file
(ignore-errors (delete-file cleanup-file))))
- :stderr log-file)))
+ :stderr stderr-buffer)))
(cj/--track-transcription process audio-file)
(cj/--notify "Transcription"
(format "Started on %s" (file-name-nondirectory audio-file)))
@@ -294,20 +306,25 @@ References TXT-FILE on success (normal urgency), LOG-FILE on failure
(format "Errored. Logs in %s" (file-name-nondirectory log-file))
'critical)))
-(defun cj/--transcription-sentinel (process event _audio-file txt-file log-file)
+(defun cj/--transcription-sentinel (process event _audio-file txt-file log-file stderr-buffer)
"Sentinel for transcription PROCESS.
EVENT is the process event string. TXT-FILE and LOG-FILE are the
-associated output files."
+associated output files. STDERR-BUFFER holds the process's stderr; its
+contents are appended to LOG-FILE so the \"Logs in <file>\" notification
+points at real error text, and the buffer is then killed so it does not
+leak per run."
(let* ((success-p (and (string-match-p "finished" event)
(= 0 (process-exit-status process))))
(process-buffer (process-buffer process)))
(cj/--write-transcript-on-success process-buffer success-p txt-file)
- (cj/--append-to-log process-buffer log-file event)
+ (cj/--append-to-log stderr-buffer log-file event)
(cj/--update-transcription-status process success-p)
(when (and success-p (not (cj/--should-keep-log success-p)))
(delete-file log-file))
(when (buffer-live-p process-buffer)
(kill-buffer process-buffer))
+ (when (buffer-live-p stderr-buffer)
+ (kill-buffer stderr-buffer))
(cj/--notify-completion success-p txt-file log-file)
(run-at-time 600 nil #'cj/--cleanup-completed-transcriptions)
(force-mode-line-update t)))
diff --git a/tests/test-transcription-process-and-sentinel.el b/tests/test-transcription-process-and-sentinel.el
index 90b56f0a5..185412934 100644
--- a/tests/test-transcription-process-and-sentinel.el
+++ b/tests/test-transcription-process-and-sentinel.el
@@ -96,6 +96,35 @@ the script and the audio path."
(should (equal (plist-get make-process-args :command)
(list script audio)))))
+(ert-deftest test-tx-start-process-stderr-is-a-buffer-not-a-path ()
+ "Normal: :stderr is a live buffer, not a file path.
+Passing a path string makes Emacs create a phantom buffer named after the
+path, so stderr never reaches the log file and that buffer leaks per run."
+ (let* ((audio (make-temp-file "cj-tx-audio-" nil ".mp3"))
+ (script (make-temp-file "cj-tx-script-"))
+ (cj/transcriptions-list nil)
+ make-process-args)
+ (set-file-modes script #o755)
+ (unwind-protect
+ (cl-letf (((symbol-function 'cj/--transcription-script-path)
+ (lambda () script))
+ ((symbol-function 'cj/--init-log-file) #'ignore)
+ ((symbol-function 'cj/--build-process-environment)
+ (lambda (_) '("FOO=bar")))
+ ((symbol-function 'make-process)
+ (lambda (&rest kwargs)
+ (setq make-process-args kwargs)
+ 'fake-process))
+ ((symbol-function 'cj/--notify) #'ignore)
+ ((symbol-function 'force-mode-line-update) #'ignore))
+ (cj/--start-transcription-process audio))
+ (delete-file audio)
+ (delete-file script))
+ (let ((stderr (plist-get make-process-args :stderr)))
+ (should (bufferp stderr))
+ (should (buffer-live-p stderr))
+ (when (buffer-live-p stderr) (kill-buffer stderr)))))
+
;;; cj/--transcription-sentinel
(ert-deftest test-tx-sentinel-success-writes-transcript-and-updates-status ()
@@ -105,6 +134,7 @@ the entry status to `complete', and fires a normal-urgency notification."
(log-file (make-temp-file "cj-tx-log-" nil ".log"))
(process-buffer (generate-new-buffer " *cj-tx-test*"))
(proc (list 'mock-process))
+ (stderr-buffer (generate-new-buffer " *cj-tx-test-stderr*"))
(cj/transcriptions-list (list (list proc "/tmp/audio.mp3"
(current-time) 'running)))
notify-urgency)
@@ -121,12 +151,15 @@ the entry status to `complete', and fires a normal-urgency notification."
(lambda (_t _m &optional u) (setq notify-urgency u))))
(cj/--transcription-sentinel proc "finished\n"
"/tmp/audio.mp3"
- txt-file log-file))
+ txt-file log-file stderr-buffer))
(when (buffer-live-p process-buffer) (kill-buffer process-buffer))
+ (when (buffer-live-p stderr-buffer) (kill-buffer stderr-buffer))
(delete-file txt-file)
(delete-file log-file))
;; success notification uses default (nil/normal) urgency.
(should-not notify-urgency)
+ ;; the stderr buffer is drained and killed, never leaked.
+ (should-not (buffer-live-p stderr-buffer))
;; entry status updated to complete.
(let ((entry (car cj/transcriptions-list)))
(should (eq (nth 3 entry) 'complete)))))
@@ -138,10 +171,14 @@ marks the entry as `error'."
(log-file (make-temp-file "cj-tx-log-" nil ".log"))
(process-buffer (generate-new-buffer " *cj-tx-fail*"))
(proc (list 'mock-fail))
+ (stderr-buffer (generate-new-buffer " *cj-tx-fail-stderr*"))
(cj/transcriptions-list (list (list proc "/tmp/audio.mp3"
(current-time) 'running)))
+ log-contents
notify-urgency)
- (with-current-buffer process-buffer (insert "stderr blob"))
+ (with-current-buffer process-buffer (insert "partial transcript"))
+ (with-current-buffer stderr-buffer (insert "whisper: CUDA out of memory"))
+ (with-temp-file log-file (insert "HEADER\n"))
(unwind-protect
(cl-letf (((symbol-function 'process-buffer)
(lambda (_) process-buffer))
@@ -153,11 +190,18 @@ marks the entry as `error'."
(lambda (_t _m &optional u) (setq notify-urgency u))))
(cj/--transcription-sentinel proc "exited abnormally\n"
"/tmp/audio.mp3"
- txt-file log-file))
+ txt-file log-file stderr-buffer)
+ (setq log-contents
+ (with-temp-buffer (insert-file-contents log-file) (buffer-string))))
(when (buffer-live-p process-buffer) (kill-buffer process-buffer))
+ (when (buffer-live-p stderr-buffer) (kill-buffer stderr-buffer))
(delete-file txt-file)
(delete-file log-file))
(should (eq notify-urgency 'critical))
+ ;; the actual stderr error text reaches the log on failure.
+ (should (string-match-p "CUDA out of memory" log-contents))
+ ;; the stderr buffer is killed, never leaked.
+ (should-not (buffer-live-p stderr-buffer))
(let ((entry (car cj/transcriptions-list)))
(should (eq (nth 3 entry) 'error)))))