tools.nrepl

Using functions reading from *in* causes "java.io.IOException: Write end dead"

Details

  • Type: Defect Defect
  • Status: Closed Closed
  • Priority: Minor Minor
  • Resolution: Completed
  • Affects Version/s: None
  • Fix Version/s: None
  • Component/s: None
  • Labels:
    None

Description

When calling (read-line) from lein repl or ccw repl, waiting around four minutes (on my machine),
evaluating any form causes the following exception:

ERROR: Unhandled REPL handler exception processing message {:code (+ 1 1), :id 08e30b70-ab0f-4a47-8937-3c040ce06f4f, :ns user, :op eval, :session 916200cc-e476-4ac4-98f6-76057a9020be}
java.io.IOException: Write end dead
	at java.io.PipedReader.ready(Unknown Source)
	at clojure.tools.nrepl.middleware.session.proxy$java.io.PipedReader$0.ready(Unknown Source)
	at java.io.BufferedReader.ready(Unknown Source)
	at java.io.FilterReader.ready(Unknown Source)
	at java.io.PushbackReader.ready(Unknown Source)
	at clojure.tools.nrepl.middleware.session$add_stdin$fn__1103.invoke(session.clj:197)
	at clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__840.invoke(middleware.clj:17)
	at clojure.tools.nrepl.middleware.session$session$fn__1093.invoke(session.clj:165)
	at clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__840.invoke(middleware.clj:17)
	at clojure.tools.nrepl.server$handle_STAR_.invoke(server.clj:18)
	at clojure.tools.nrepl.server$handle$fn__1132.invoke(server.clj:27)
	at clojure.core$binding_conveyor_fn$fn__4130.invoke(core.clj:1836)
	at clojure.lang.AFn.call(AFn.java:18)
	at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
	at java.util.concurrent.FutureTask.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

While googling a bit on this and without concrete knowledge of the implementation in nrepl,
it seems like closing a thread that wrote to PipedWriter, before closing the writer or writing to it from another thread,
causes PipedReader#ready to throw that exception.

Activity

Hide
Colin Jones added a comment -

OK, this works for me against REPLy.

I'd have preferred to avoid the dynamic binding (skipping-eol), but I haven't been able to think of another way to accomplish what it does. The problem is that we don't want to send another :need-input message just to clear newlines, but we don't have any visibility into what's in the LineNumberingPushbackReader's buffer (neither the full deal, nor the pushback). My initial thought was to replace the LineNumberingPushbackReader directly, with a custom thing, but I think the assumptions around that class in `read` and `read-line` are too concretely baked in to allow it (especially before 1.5, when the buffer size became configurable).

And there's no room for adding bits to the Reader interface, so in order to effect a behavior difference in the Reader, depending on what the high-level state is, I don't know that there are any options aside from dynamic binding or an atom (and of the two, a var seemed more robust, no worries about finally & friends).

Show
Colin Jones added a comment - OK, this works for me against REPLy. I'd have preferred to avoid the dynamic binding (skipping-eol), but I haven't been able to think of another way to accomplish what it does. The problem is that we don't want to send another :need-input message just to clear newlines, but we don't have any visibility into what's in the LineNumberingPushbackReader's buffer (neither the full deal, nor the pushback). My initial thought was to replace the LineNumberingPushbackReader directly, with a custom thing, but I think the assumptions around that class in `read` and `read-line` are too concretely baked in to allow it (especially before 1.5, when the buffer size became configurable). And there's no room for adding bits to the Reader interface, so in order to effect a behavior difference in the Reader, depending on what the high-level state is, I don't know that there are any options aside from dynamic binding or an atom (and of the two, a var seemed more robust, no worries about finally & friends).
Hide
Chas Emerick added a comment -

I had no idea that PipedReader & co. actually track the threads that touch them. That makes them entirely inappropriate for the *in* case.

+1 to your plan, thanks for chasing this!

Show
Chas Emerick added a comment - I had no idea that PipedReader & co. actually track the threads that touch them. That makes them entirely inappropriate for the *in* case. +1 to your plan, thanks for chasing this!
Hide
Colin Jones added a comment -

I found this related article: http://techtavern.wordpress.com/2008/07/16/whats-this-ioexception-write-end-dead/

So it seems like we may need something a little more robust. I'm thinking just a LinkedBlockingQueue that gets written to on input, with a Reader proxy over the top to hook up to Clojure's LineNumberingPushbackReader. I've started fiddling with that idea, passing most of the tests already, and I think this is the right path to go down in general. Yell at me if you see a problem with this plan.

Show
Colin Jones added a comment - I found this related article: http://techtavern.wordpress.com/2008/07/16/whats-this-ioexception-write-end-dead/ So it seems like we may need something a little more robust. I'm thinking just a LinkedBlockingQueue that gets written to on input, with a Reader proxy over the top to hook up to Clojure's LineNumberingPushbackReader. I've started fiddling with that idea, passing most of the tests already, and I think this is the right path to go down in general. Yell at me if you see a problem with this plan.

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: