Some Classes with a public void close() don't implement AutoCloseable

Florian Weimer fw at deneb.enyo.de
Sat Apr 18 15:18:08 UTC 2020


* John Rose:

> On Apr 17, 2020, at 9:35 AM, Florian Weimer <fw at deneb.enyo.de> wrote:
>> 
>> * forax:
>> 
>>> - until lambdas are retrofitted as inline types, i'm worry people
>>> will use forceTryable with a ReentrantLock, try(var __ =
>>> forceTryable(lock::unlock)) { ... }  because most of the time, the
>>> lambda will not be allocated but not always, sometimes it may still
>>> be found escaping by the JITs.
>> 
>> The JDK itself routinely does something very similar when registering
>> cleaners.  Is this lack of robustness in the presence of OOMEs a
>> quality-of-implementation issue?  Or less or more important?
>
> Turning this around in my mind, it seems like the root cause of
> this non-robustness is a minor mis-feature of TWR.  TWR says
> to itself, “I only have one job, and that is to call close”.  That’s
> fine, but that means that means that the object to be closed
> must be created *and* opened before TWR takes control.
> If the closing operation is mediated by an adapter, the adapter
> will almost certainly be created *after* the original (unadapted)
> object is created and opened.  This is the point where an OOME
> could bollix things up.  To defend against such failures, TWR
> would have to define a distinct point where the object is opened.
> This point would be a precise state transition immediately before
> entry into the block covered by catches and the `finally` block.

I think these issues were discussed and are the reason why
try-with-resources requires the introduction of a new variable, to
make clearer that there is no ownership in the evaluation of the
expression.

I guess this probably was onl partially successful, due to the
wrapping of streams and code like this:

  try (PrintStream ps = new PrintStream(new FileOutputStream
                                        (filename))) {
      doCertReq(alias, sigAlgName, ps);
  }

It should have been:

  try (InputStream in = new FileOutputStream (filename);
      PrintStream ps = new PrintStream(in)) {
      doCertReq(alias, sigAlgName, ps);
  }

> How might this mis-feature be corrected?  Maybe it can’t.
> Maybe there’s no way to deal with lost closes other than
> educating programmers to avoid allocations or other
> computations in the head of the TWR, at least allocations
> after the logical open.  (Those are the ones which might cause
> an OOME or SOE that would lead to a lost close.)

In the streams example above, the consequences of the wrong style may
not be so bad because (at least in OpenJDK 8), garbage collection
eventually cleans up the temporary resource leak.

With the unlock example from the proposed function, the effect would
be permanent, though.

However, with the move from finalize() to Cleaners, the OpenJDK itself
has given up on avoiding permanent resource leaks due to OOME at
inopportune moments, I think.

> But (and this is why I’m going on at length here) maybe there
> is a way to give the user more help avoiding lost closes.
> It seems that if TWR were willing to define open operations
> separately, some progress could be made.  Suppose the object
> at the head of a TWR implemented an optional new interface
> `java.lang.AutoOpenable`.  In such cases, the TWR would
> call a nullary `open` operation on that object, immediately
> before entering the block covered by the `finally` clause
> that calls `close`. Would this help?  Yes, it would, because
> the designer of the abstraction at the head of the TWR could
> take care to do all argument validation and storage allocation
> *without* doing the actual logical open or allocate the resource
> that needs to be closed (or seize the lock).  The evaluation
> of the head expression(s) of the TWR would (as before) not
> be under any finally clause or catches.  The new feature here
> would be that the designer of the abstraction (at the head
> of the TWR) would know that the expression creating
> the abstraction would do all necessary storage allocation,
> adapter creation, argument validation, and (if necessary)
> stack banging, *before* the final critical opening step.
> It would not be up to the end user; it would be more under
> the control of the abstraction, to sequence the preparatory
> steps *before* the logical open.

Is this proposal similar to context managers for Python's with
statement?

I don't think this helps with the chaining problem or the cleaners
issue.  Maybe those issues are unrelated, after all.


More information about the discuss mailing list