MayHoldCloseableResource vs AutoCloseable

Zhong Yu zhong.j.yu at gmail.com
Mon Jul 8 10:28:35 PDT 2013


You are borrowing arguments from the dynamic type camp:)

On Mon, Jul 8, 2013 at 12:17 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
> (2) was already considered, and rejected, by the EG.  It puts a burden on
> users who just want to write small scripty programs like:
>
>   Files.walk(dir).forEach(f -> ...);
>
> without being hit over the head with "someone thinks you should bow to the
> resource management gods here before continuing."
>
> Instead, we prefer to put the burden on: know what's in your streams. If it
> requires closing, we'll help by supporting AutoCloseable and therefore TWR,
> but only if you ask for resource management.  If you don't ask, we won't get
> in your way.
>
>
> On 7/8/2013 1:05 PM, Zhong Yu wrote:
>>
>> On Mon, Jul 8, 2013 at 10:06 AM, Paul Benedict <pbenedict at apache.org>
>> wrote:
>>>
>>> Thanks Brian. So I go back to my question. Why not just use the
>>> annotation
>>> and dump the interface? If any AutoCloseable sub-interface is annotated
>>> with @MayHoldResource, resource leak warnings and catching can be
>>> disabled.
>>
>>
>> That'll require we annotate majority of methods in JDK that return
>> Streams (which require no close()). In Brian's solution only a few IO
>> methods need to be annotated.
>>
>> However the solution may not work in some applications where most
>> Streams hold resources and require close(); it'll be a nuance to
>> annotate most of their Stream yielding methods.
>>
>> One of EG's motivations to add close() directly on Stream seems to be
>> that if a stream is associated with some resources, a derived stream
>> should be associated with these resources too, and the responsibility
>> to free these resources can be transferred from the original stream to
>> the derived stream. However then the programmer has to mentally keep
>> track of the transfer of responsibility. It's arguably more
>> complicated than pinning the responsibility on the original stream.
>>
>> What I mean is, in the previous design the IO methods returned
>> CloseableStream<T>
>>
>>      interface CloseableStream<T> extends AutoCloseable, Stream<T>
>>
>> and EG didn't like it because, say, CloseableStream.map() does not
>> return a CloseableStream, so the derived streams are not closeable.
>> Adding close() to all Streams solves that problem.
>>
>> One can object that why do we need to make derived streams closeable
>> too? All we need to ensure is to close the original stream; we don't
>> need to close derived streams. If we need to leak derived stream to
>> another scope, e.g. return it from a method body, we can then wrap it
>> in CloseableStream, thus transferring close() from the original stream
>> to this derived stream.
>>
>> What we really need here is an intersection type Stream&Closeable;
>> Java doesn't have denotable intersection type, but there are several
>> ways to approximate that effect. We've seen two solutions with
>> subtyping. There are also 3 other solutions with composition:
>>
>> 1.
>>          Pair<Stream<Path>, Closeable> listFiles(dir);
>>
>> 2.
>>          CloseableAnd<Stream<Path>> listFiles(dir);
>>
>>          interface CloseableAnd<X> extends Closeable
>>              X getX();
>>
>> 3.
>>          StreamAnd<Closeable, Path> listFiles(dir);
>>
>>          interface StreamAnd<X, T> extends Stream<T>
>>              X getX();
>>
>>
>> My personal favorite is (2). It's slightly more verbose than
>> CloseableStream at use site, but it's more generic and it works on any
>> objects associated with GC-resistant resources.
>>
>>
>> Zhong Yu
>>
>>
>>
>>
>>> The current sub-interface, imo, does not achieve anything.
>>>
>>>
>>> On Mon, Jul 8, 2013 at 9:54 AM, Brian Goetz <brian.goetz at oracle.com>
>>> wrote:
>>>
>>>> This was all extensively hashed out on the EG list, you should read the
>>>> discussions.
>>>>
>>>> The difference is in the presumption.  An AC object is *assumed to*
>>>> require closing, unless you can demonstrate a reason why it does not
>>>> require closing (e.g., ByteArrayInputStream holds no GC-resistent
>>>> resources
>>>> and its close() method does nothing.)
>>>>
>>>> A MHCR object should be presumed *not to* require closing, unless you
>>>> know
>>>> that it does (e.g., an IO-backed stream.)
>>>>
>>>> Static analysis tools are one audience for this distinction, but there's
>>>> another important one: the brains of people who read code.
>>>>
>>>> Not closing something that holds a file handle is a big problem, but at
>>>> the same time, we don't want to ask people to write:
>>>>
>>>> int sum =
>>>>    list.stream()
>>>>        .filter(...)
>>>>        .map(...)
>>>>        .sum();
>>>>
>>>> as
>>>>
>>>> int sum;
>>>> try (IntStream s = list.stream()
>>>>                         .filter(...)
>>>>                         .map(...)) {
>>>>      sum = s.sum();
>>>> }
>>>>
>>>> just because list *might* hold a file handle.
>>>>
>>>> On the other hand, we want streams to be closeable, because if they do
>>>> hold resources, we want for derived streams (like concat(a, b)) to be
>>>> able
>>>> to ass the close message to their constituent components.
>>>>
>>>> So the problem here is "must implement a close mechanism, but 99% of the
>>>> time, users should pretend they don't and just go on with normal
>>>> coding."
>>>>   And if they're going to implement a close mechanism, NOT implementing
>>>> AC
>>>> is dumb.
>>>>
>>>> The reality is there are lots of places where we rely on users to know
>>>> stuff that can't be determined from the static type system.  For
>>>> example:
>>>>
>>>>   - This object is thread-confined (or thread-safe), so I can access its
>>>> state without additional synchronization
>>>>   - This object is immutable, so I can freely share it with other code
>>>> without copying it
>>>>
>>>> We routinely rely on the user to know when certain things are safe, and
>>>> very often the user does know these things.  The same is true with "does
>>>> this stream hold a GC-resistent resource like a file handle."  Because
>>>> streams are designed to be created and traversed in the same expression,
>>>> most of the time, the user already knows with certainty.  In those cases
>>>> where a framework has to manipulate streams (like Stream.concat) and
>>>> therefore has to operate in generality, it can take the conservative
>>>> route
>>>> and assume that closing is needed.
>>>>
>>>> You can consider this as a loose end from Coin, where AC only considered
>>>> the case where it was reasonable to presume that failure to close was an
>>>> error.  The case where closing is supported but not expected was not
>>>> handled by AC.  MHCR attempts to address that.  Is it beautiful?
>>>> Certainly
>>>> not.  But, as Doug said, it "provides something better than any other
>>>> related schemes I know."  If the name weren't so clunky, I daresay you
>>>> might not have even noticed.
>>>>
>>>>
>>>>
>>>> On 7/8/2013 10:20 AM, Paul Benedict wrote:
>>>>
>>>>> What are the semantic differences between these two interfaces? I come
>>>>> away
>>>>> with no programming difference (i.e., you will always need to close the
>>>>> stream because it *could* hold a resource); but it's more of a hint to
>>>>> IDEs
>>>>> not to display a "resource leak" warning if try-with-resources is not
>>>>> used.
>>>>>
>>>>> Am I correct? The only reason I ask is because it seems this interface
>>>>> can
>>>>> be done without. I think it would be much more palatable to use just
>>>>> the
>>>>> annotation only rather than the sub-interface.
>>>>>
>>>>> Paul
>>>>>
>>>>>
>>>
>


More information about the lambda-dev mailing list