orthecreedence / cl-async

Asynchronous IO library for Common Lisp.
MIT License
273 stars 40 forks source link

I found a strange behavior on OSX 10.8.5 sbcl 1.1.9 #84

Closed odie2630463 closed 11 years ago

odie2630463 commented 11 years ago

I try this code in sublime Text 2 REPL,

(defun my ()
  (as:delay (lambda () (print "hello world")) :time 2)
  (as:delay (lambda () (print "why")) :time 4)
  (as:delay (lambda () (print "still here")) :time 8)
  (as:delay (lambda () (print "last one!")) :time 1))

(as:start-event-loop #'my :catch-app-errors t)

but I got the response "last one" => 1s "hello world" => 2s "why" => 8s "still here" => 8s

I try many time, I found last two events maybe same time return to REPL Can someone tell me why? or all is my mistake? thanks !

orthecreedence commented 11 years ago

@odie2630463 What behavior are you expecting? This looks normal to me.

The way async works is that things like delay (as well as sending/receiving TCP data) create an event that will fire when certain criteria is met, return immediately after being called, and then run their callback once the event is fired (sometime in the future).

So with your delay example, your first delay creates a 2 second time, returns immedately, then a 4 second timer is created, then an 8 second, then a 1 second. All of these calls return immediately, so calling the "why" delay does not wait for the "hello world" delay before firing.

Once your function is finished running, it returns control to the event loop, which is continuously monitoring for events that have fired (in this case, delay will fire its event when its timer runs out). So because all these timers were created at the same time, they fire in order of how short their delay is (1s, 2s, 4s, 8s).

Once all the delay timers have fired, the event loop checks if there are any pending events left in the event loop. If there are not, it returns control to the REPL.

If you want your delays to fire in the order defined, you need to nest them:

(defun my ()
  (as:delay
    (lambda ()
      (print "hello world"))
      (as:delay
        (lambda ()
          (print why)
          ...)
        :time 4))
    :time 2))
(as:start-event-loop #'my :catch-app-errors t)

This is what's known as "callback hell." Nesting a bunch of callbacks (formally known as Continuation Passing Style...CPS) to almost replicate what a call stack gives you for free. This is why async is hard =].

However, you can somewhat make it nicer using futures:

(ql:quickload :cl-async-future)

(defun my-delay (time)
  (let ((future (cl-async-future:make-future)))
    (as:delay (lambda () (cl-async-future:finish future)) :time time)
    future))

(defun my ()
  (wait-for (my-delay 2)
    (print "hello world")
    (wait-for (my-delay 4)
      (print "why")
      ...)))
(as:start-event-loop #'my :catch-app-errors t)
odie2630463 commented 11 years ago

(defun my () (as:delay (lambda () (print "hello world")) :time 2) (as:delay (lambda () (print "why")) :time 4) (as:delay (lambda () (print "still here")) :time 8) (as:delay (lambda () (print "last one!")) :time 1))

(as:start-event-loop #'my :catch-app-errors t)

i expect the "print why" will fire on time 4 but in my REPL it fire on the time 8 same as the "print still here" is this normal ? thanks your reply !!!

orthecreedence commented 11 years ago

What lisp implementation (name, version) and what OS (name, version, 32/64 bit) are you on?

orthecreedence commented 11 years ago

Oh, also, I've never used the Sublime Text REPL, it may be doing some buffering. What happens if you run your code just in a standard lisp REPL from the command line?

odie2630463 commented 11 years ago

on OSX 10.8.5 sbcl 1.1.9 i run the same code on the sbcl REPL

(defun my () (as:delay (lambda () (print "hi")) :time 2) (as:delay (lambda () (print "hi hi")) :time 6))

(as:start-event-loop #'my :catch-app-errors t)

i got the response "print hi" and "print hi hi" same time at time 6 but i use cl-async to write the little server & client work fine thanks your reply !

orthecreedence commented 11 years ago

I'm able to reproduce on SBCL 1.0.22 on FreeBSD 9.1 x86_64. It looks like some sort of output buffering issue. Replacing the (print "hi") statements with (format t "hi~%") seems to fix it, as well as this:

(defun do-print (msg)
  (print msg)
  ;; flush the *standard-output* buffer to the screen
  (force-output))

(defun my ()
  (as:delay (lambda () (do-print "hi")) :time 2)
  (as:delay (lambda () (do-print "hi hi")) :time 6))

(as:start-event-loop #'my :catch-app-errors t)

This waits 2 seconds, prints "hi" then waits another 4 seconds and prints "hi hi" on another line.

So I think cl-async is working fine, but SBCL is just being selective about when to send the data you're printing to the screen =]. Closing this for now, please re-open if you find the above doesn't work.