Welcome! Please see the About page for a little more info on how this works.

+4 votes
in Refs, agents, atoms by
closed by

If a delay is currently processing, calling realized? causes the caller to block until the delay has completed and then returns true.

The behaviour I would have expected is that it should return false, which is what happens with futures. After all, if you wanted to wait until it was completed, you would deref it.

(also there don't seem to be any tags or categories related to concurrent programming, but since delay is part of clojure.core, I assume this is the correct place to ask)

closed with the note: Fixed in 1.11.0-alpha2

2 Answers

+1 vote
Thanks for getting onto this so quickly Alex.

Yesterday I also noticed that just trying to print the value of executing delay in the REPL without derefing it will also block. However, I'm 99% sure that's just because whatever is building the string representation is also calling "realized?" so I don't think that's a different bug.
edited by
Given the docstring of realized? I agree that returning false is the correct behavior if the delay is currently in-flight. That said, there are definitely times where I have been using a delay and realized? in concurrent contexts to know "has the delay been forced yet?" to decide if I want to replace the delay (in an atom) or wait for the already in-flight process to complete. I could use deref for that except that I don't want to *start* the process if it's not already in flight.

If i'm understanding correctly after 1.11 I can no longer use realized? to detect this case. Is there going to be a supported way of answering "has the delay been forced yet?" or do I now need to create my own constructs?

A second isForced() / forced? predicate that becomes true/visible to all threads as soon as the synchronized block in deref is entered (but before the thunk has started/completed) would be one possible solution to what I'm asking for.
I think what you’re looking for is not part of the public api of delay (really IDeref / IPending).
0 votes

In the meantime you can extend clojure.lang.Delay to my.Delay and override isRealized to not be synchronized, and have a new delay:

(defmacro my-delay [& body]
  (list 'new 'my.Delay (list* `^{:once true} fn* [] body)))
I think since `realized?` and `force` are no longer synchronized, the concurrency primitive service that delay provides has changed.  Now multiple callers from multiple threads can potentially `force` the delay simultaneously if the work is not yet done, meaning the thunk may be called multiple times before being cached.
Removing synchronized on isRealized() does not change the deref code or its semantics across threads (which are still synchronized in deref()).

Because fn is volatile, publication of this value is safe and will be seen across threads.