orthecreedence / blackbird

Common Lisp promise implementation (successor to cl-async-future)
83 stars 10 forks source link

Unexpected behaviour #22

Closed PuercoPop closed 8 years ago

PuercoPop commented 8 years ago

Oi, I'm unsure if this is a bug, I'm misunderstanding how promises are supposed to work or making a simple mistake.

When writing some convenience macros for when working with rethinkdb the &body of both macros as illustrated by the code below. Any pointers as to what could be going wrong?

(eval-when (:compile-toplevel :execute)
  (ql:quickload '(alexandria cl-rethinkdb cl-async blackbird)))

(defpackage #:foo
  (:use #:cl #:alexandria #:cl-rethinkdb #:cl-async #:blackbird))

(in-package #:foo)

(defvar *host* "127.0.0.1"
  "The address of the database")
(defvar *port* 28015)

(defmacro with-db ((socket-name &key (host *host*) (port *port*))
                   &body body)
  "Run the BODY with a socket bound to SOCKET-NAME "
  `(alet ((,socket-name (connect ,host ,port)))
     (unwind-protect (progn
                       ,@body)
       (disconnect ,socket-name))))

(defmacro with-query ((result query) &body body)
  "Run the BODY with the result of QUERY bound to RESULT "
  (with-gensyms (socket-name)
    `(with-db (,socket-name)
       (alet ((,result (run ,socket-name (r ,query))))
         (progn
           ,@body
           ,result)))))

#+works
(as:with-event-loop ()
  (alet* ((sock (connect "127.0.0.1" 28015))
          (query (r (:db-list)))
          (value (run sock query)))
    (prog1
        value
      (format t "~A~%" value)
      (disconnect sock))))

#+returns-0-does-not-print-anything
(as:with-event-loop ()
  (with-db (sock)
    (alet* ((query (r (:db-list)))
            (value (run sock query)))
      (prog1
          value
        (format t "~A~%" value)))))

#+does-not-work
(progn
  (defvar *foo*)

  (as:with-event-loop ()
    (with-query (value (:db-list))
      (setf *foo* 1)
      (format t "~A~%" value)))

  *foo*)
;; *foo* is unbound, nothing is printed

P.D. Happy Holidays and thanks for cl-async, wookie and cl-rethinkdb!

orthecreedence commented 8 years ago

I see the problem. with-query uses unwind-protect to disconnect the socket when (progn ,@body) finishes. This is fine in synchronous code, but in our case, the control is returned immediately from the (progn ...) after hitting the first async operation, which in turn then closes the socket before the results can even come back, and causing the event loop to exit prematurely since there are no events left to process.

Try this:

(defmacro with-db ((socket-name &key (host *host*) (port *port*))
                   &body body)
  "Run the BODY with a socket bound to SOCKET-NAME "
  `(alet ((,socket-name (connect ,host ,port)))
     (finally (progn ,@body)
       (disconnect ,socket-name))))

I'm at work and haven't had a chance to test this, but I think it's what you want.

finally is the blackbird promise version of unwind-protect. No matter what happens inside of the body, the disconnect call will be made at the end of the promise chain returned by the body.

PuercoPop commented 8 years ago

Thanks that does it.