[CLJ-232] Locals cleared too aggressively on delay Created: 30/Dec/09 Updated: 24/Aug/10 Resolved: 24/Aug/10 |
|
| Status: | Closed |
| Project: | Clojure |
| Component/s: | None |
| Affects Version/s: | None |
| Fix Version/s: | Release 1.2 |
| Type: | Defect | ||
| Reporter: | Anonymous | Assignee: | Rich Hickey |
| Resolution: | Completed | Votes: | 0 |
| Labels: | None | ||
| Description |
|
Steve Gilardi and I noticed some strange behaviour with local clearing in conjunction with delay: (defn do-something [x] (defn clear-locals [x] (force (clear-locals :argument)) It seems the locals are getting cleared a little too aggressively here. I did some digging to discover that delay is a thin wrapper around an fn, but using an fn in this context (rather than a delay) does not trigger the problem. Also I found that throwing an exception directly inside the try block (rather than in a function called from the try block) did not trigger it. I was able to reproduce in the new branch, the master branch, and 1.0.0, so it's not related to the more recent locals-clearing changes. |
| Comments |
| Comment by Assembla Importer [ 24/Aug/10 5:43 AM ] |
|
Converted from http://www.assembla.com/spaces/clojure/tickets/232 |
| Comment by Assembla Importer [ 24/Aug/10 5:43 AM ] |
|
technomancy said: [file:d6rA1a9ASr3RHaeJe5afGb]: repro case |
| Comment by Assembla Importer [ 24/Aug/10 5:43 AM ] |
|
chouser@n01se.net said: ((let [x :foo]
(#^{:once true} fn* []
(try (#(throw (Exception.)))
(catch Exception e
(println "x:" x))))))
</code></pre>
The same problem is visible when a finally clause uses a closed-over:
<pre><code> ((let [x :foo]
(#^{:once true} fn* []
(try (#(throw (Exception.)))
(finally
(println "x:" x))))))
Both of these print x: nil when they should print x: :foo I think the problem is that, because the last expr in a try block produces the value that will be returned, it is compiled in a "return" context (a.k.a. tail position), therefore the locals are cleared before calling the final function. But if that final call throws an exception, you end up in the catch clause with your locals cleared. emitClearLocals protects against this problem by using localsUsedInCatchFinally, but emitClearCloses (used only when :once is true) does not. The solution is so very far beyond me. I guess the simplest might be to skip clearing of closed-overs that are used in catch/finally clauses, but I don't think those are currently tracked. It looks like closeOver could be made to track these as well, but I'm lost as to the relationships between the various instances in that code. |
| Comment by Assembla Importer [ 24/Aug/10 5:43 AM ] |
|
danlarkin said: Kevin Downey and I just stumbled on this problem again. Here's our smallest repro case: (defn throwsomething [] (throw (Exception.))) (defn foo [bar] @(delay (try (throwsomething) (catch Exception e (nil? bar))))) </code></pre> We came up with two workarounds, one is to wrap everything inside the delay in a let capturing the "bar" scope, like this: <pre><code> (defn foo [bar] @(delay (let [bar bar] (try (throwsomething) (catch Exception e (nil? bar)))))) </code></pre> and the other is to not have the exception-throwing call in the tail position, like this: <pre><code> (defn foo [bar] @(delay (try (let [t (throwsomething)] t) (catch Exception e (nil? bar))))) |
| Comment by Assembla Importer [ 24/Aug/10 5:43 AM ] |
|
richhickey said: (In [[r:93fecbd825c26e2570f8449cd64d0df0cc520c1d]]) fold closes clearing into the path system, fixes #232 Branch: master |