From adb605716f2feda57a9ab5ea0d1d979e51ce8915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Wed, 14 Jan 2026 10:46:24 +0000 Subject: [PATCH] Jsonrpc: don't let remote endpoint requests go unanswered Previously, 'quit' could cause remote endpoints to never get a reply and thus sometimes hang. Ensure we always reply. Also, give the application a chance to signal jsonrpc-error with the served code=32000, meaning "no error". * doc/lispref/text.texi (JSONRPC Overview): Rework section on request dispatchers. * lisp/jsonrpc.el (jsonrpc-connection-receive): Rework. --- doc/lispref/text.texi | 21 +++++++++++++++------ lisp/jsonrpc.el | 39 +++++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index d88fb99a6ed..a313480944b 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -6014,12 +6014,21 @@ must be a Lisp object that can be serialized as JSON (@pxref{Parsing JSON}). The result is forwarded to the server as the JSONRPC @code{result} object. A non-local return, achieved by calling the function @code{jsonrpc-error}, causes an error response to be sent to -the server. The details of the accompanying JSONRPC @code{error} -object are filled out with whatever was passed to -@code{jsonrpc-error}. A non-local return triggered by an unexpected -error of any other type also causes an error response to be sent -(unless you have set @code{debug-on-error}, in which case this calls -the Lisp debugger, @pxref{Error Debugging}). +the server. A non-local return triggered by an unexpected error of any +other type also causes a response to be sent. The debugger is never +called (unless you have set @code{debug-on-error}, in which case the +Lisp debugger may be called, @pxref{Error Debugging}). + +The details of the accompanying JSONRPC @code{error} object are filled +out automatically (in the case of unexpected errors) or with whatever +was passed to @code{jsonrpc-error} (in the case of explicit calls). + +Exceptionally, an explicit call to @code{jsonrpc-error} which sets +@code{:code} to 32000 and @code{:data} to any JSON object has the +meaning of ``no error'' and triggers a normal response to the remote +endpoint with @code{result} being set to @code{:data}. This is useful +if the application wants to treat some non-local exits such as user +quits as benign. @findex jsonrpc-convert-to-endpoint @findex jsonrpc-convert-from-endpoint diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el index 0cd5097e5f0..74a59a04095 100644 --- a/lisp/jsonrpc.el +++ b/lisp/jsonrpc.el @@ -327,22 +327,29 @@ dispatcher in CONN." (and method id) (let* ((debug-on-error (and debug-on-error (not jsonrpc-inhibit-debug-on-error))) - (reply - (condition-case-unless-debug _ignore - (condition-case oops - `(:result ,(funcall rdispatcher conn (intern method) - params)) - (jsonrpc-error - `(:error - (:code - ,(or (alist-get 'jsonrpc-error-code (cdr oops)) - -32603) - :message ,(or (alist-get 'jsonrpc-error-message - (cdr oops)) - "Internal error"))))) - (error - '(:error (:code -32603 :message "Internal error")))))) - (apply #'jsonrpc--reply conn id method reply))) + reply) + (unwind-protect + (setq + reply + (condition-case oops + `(:result + ,(funcall rdispatcher conn (intern method) params)) + (jsonrpc-error + (let* ((data (cdr oops)) + (code (alist-get 'jsonrpc-error-code data)) + (msg (alist-get 'jsonrpc-error-message + (cdr oops)))) + (if (eq code 32000) ;; This means 'no error' + (when-let* ((d (alist-get 'jsonrpc-error-data + data))) + `(:result ,d)) + `(:error + (:code ,(or code -32603) + :message ,(or msg "Internal error")))))))) + (unless reply + (setq reply + `(:error (:code -32603 :message "Internal error")))) + (apply #'jsonrpc--reply conn id method reply)))) (;; A remote notification method (funcall ndispatcher conn (intern method) params))