@@ -72,6 +72,26 @@ session by, for example, executing a src-block."
7272 :group 'ob-jupyter
7373 :type 'boolean )
7474
75+ (defcustom jupyter-org-queue-requests nil
76+ " Whether or not source block evaluations should be queued.
77+ When this variable is nil and, for example, multiple source
78+ blocks are executed in rapid succession the underlying
79+ \" execute_request\" messages are sent to the kernel immediately
80+ and are queued on the kernel side so that when one of the source
81+ blocks raises an error, the kernel will typically just execute
82+ the next \" execute_request\" message queued up so the effect is
83+ that source blocks that come after the failed one are executed.
84+ Some may find this behavior undesirable.
85+
86+ Instead, when this variable is non-nil, the \" execute_request\"
87+ messages of the source blocks are queued on the client side and
88+ whenever one of the source blocks raises an error, all of the
89+ queued \" execute_request\" messages are aborted and don't get
90+ sent to the kernel so the effect is that source blocks that come
91+ after the failed one are not executed."
92+ :group 'ob-jupyter
93+ :type 'boolean )
94+
7595(defcustom jupyter-org-resource-directory " ./.ob-jupyter/"
7696 " Directory used to store automatically generated image files.
7797See `jupyter-org-image-file-name' ."
@@ -111,7 +131,16 @@ See also the docstring of `org-image-actual-width' for more details."
111131 " MIME types handled by Jupyter Org." )
112132
113133(defclass jupyter-org-client (jupyter-repl-client)
114- ())
134+ ((most-recent-request
135+ :type (or jupyter-request null )
136+ :initform nil
137+ :initarg :most-recent-request
138+ :documentation " The most recently sent request." )
139+ (last-queued-request
140+ :type (or jupyter-request null )
141+ :initform nil
142+ :initarg :last-queued-request
143+ :documentation " The last queued request." )))
115144
116145(cl-defstruct (jupyter-org-request
117146 (:include jupyter-request)
@@ -400,6 +429,96 @@ to."
400429 (org-with-point-at (jupyter-org-request-marker req)
401430 (run-hooks 'org-babel-after-execute-hook )))))
402431
432+ ; ;; Queueing requests
433+
434+ (defun jupyter-org-abort (req )
435+ " Abort REQ.
436+ Set the request as being idle. Remove any indication that REQ is
437+ a running execute_request from the Org buffer. Publish an abort
438+ message down the chain of subscribers to the REQ's message
439+ publisher to indicate that any subsequent, queued, requests
440+ should also be aborted."
441+ (setf (jupyter-request-idle-p req) t )
442+ (let ((client (jupyter-request-client req)))
443+ (when (eq (oref client last-queued-request) req)
444+ (oset client last-queued-request nil )))
445+ (jupyter-org--remove-overlay req)
446+ (jupyter-org--clear-async-indicator req)
447+ (let ((marker (jupyter-org-request-marker req)))
448+ (message (format " Source block execution in %s at position %s canceled "
449+ (buffer-name (marker-buffer marker))
450+ (marker-position marker))))
451+ (with-demoted-errors " Error while aborting subscribers: %S"
452+ (jupyter-run-with-io
453+ (jupyter-request-message-publisher req)
454+ ; ; Propagate the abort down the chain of queued requests.
455+ (jupyter-publish 'abort )))
456+ (jupyter-unsubscribe))
457+
458+ (defun jupyter-org-maybe-queued (dreq )
459+ " Return a monadic value that either sends or continues to delay DREQ.
460+ DREQ is an already delayed request, as returned by
461+ `jupyter-request' and friends. When the value is bound to a
462+ client, using e.g. `jupyter-run-with-client' , send DREQ if there
463+ are no queued requests otherwise queue DREQ. The value returns
464+ the unboxed request contained in DREQ.
465+
466+ If the variable `jupyter-org-queue-requests' is nil, just send
467+ the request immediately instead of attempting to queue it."
468+ (if (not jupyter-org-queue-requests)
469+ (jupyter-sent dreq)
470+ (jupyter-mlet* ((client (jupyter-get-state))
471+ (req dreq))
472+ (let* ((send
473+ (lambda (req )
474+ (jupyter-run-with-client client
475+ (jupyter-mlet* ((req (jupyter-sent
476+ (jupyter-return req))))
477+ (oset client most-recent-request req)
478+ (jupyter-run-with-io
479+ (jupyter-request-message-publisher req)
480+ (jupyter-subscribe
481+ (jupyter-subscriber
482+ (lambda (msg )
483+ (when (or (eq msg 'abort )
484+ (equal (jupyter-message-type msg) " execute_reply" ))
485+ (when (eq (oref client most-recent-request) req)
486+ (oset client most-recent-request nil ))
487+ (jupyter-unsubscribe))))))
488+ (when (eq (oref client last-queued-request) req)
489+ (oset client last-queued-request nil ))
490+ (jupyter-return req)))))
491+ (queue
492+ ; ; Subscribe REQ to the message publisher of QREQ such that
493+ ; ; REQ is sent or aborted when QREQ receives an
494+ ; ; execute_reply.
495+ (lambda (qreq req )
496+ (let ((pub (jupyter-request-message-publisher qreq)))
497+ (jupyter-run-with-io pub
498+ (jupyter-subscribe
499+ (jupyter-subscriber
500+ (lambda (msg )
501+ (if (eq msg 'abort )
502+ (jupyter-org-abort req)
503+ (pcase (jupyter-message-type msg)
504+ (" execute_reply"
505+ (jupyter-with-message-content msg (status)
506+ (if (equal status " ok" )
507+ (funcall send req)
508+ (jupyter-org-abort req)))
509+ (jupyter-unsubscribe))))))))))))
510+ (let ((mreq (oref client most-recent-request)) qreq)
511+ (cond
512+ ((null mreq)
513+ (funcall send req))
514+ ((setq qreq (oref client last-queued-request))
515+ (funcall queue qreq req)
516+ (oset client last-queued-request req))
517+ (t
518+ (funcall queue mreq req)
519+ (oset client last-queued-request req)))
520+ (jupyter-return req))))))
521+
403522; ;; Completion in code blocks
404523
405524(defvar jupyter-org--src-block-cache nil
0 commit comments