Skip to content

Remove CloseQuietly and migrate its usages to other methods.#10247

Merged
clintropolis merged 22 commits intoapache:masterfrom
gianm:fix-closequietlys
Oct 24, 2021
Merged

Remove CloseQuietly and migrate its usages to other methods.#10247
clintropolis merged 22 commits intoapache:masterfrom
gianm:fix-closequietlys

Conversation

@gianm
Copy link
Copy Markdown
Contributor

@gianm gianm commented Aug 6, 2020

CloseQuietly.close is a method that closes something. It suppresses and logs any
IOExceptions it gets, but re-throws any non-IOExceptions. It is used extensively and it
seems likely to me that most usages did not intend to have these specific behaviors.
So this patch gets rid of the method and migrates call sites to various more sanely
behaving methods.

These other methods include:

  1. New method CloseableUtils.closeAndWrapExceptions, which wraps IOExceptions
    in RuntimeExceptions for callers that just want to avoid dealing with
    checked exceptions. Most usages were migrated to this method, because it
    looks like they were mainly attempts to avoid declaring a throws clause,
    and perhaps were unintentionally suppressing IOExceptions.
  2. New method CloseableUtils.closeInCatch, designed to properly close something
    in a catch block without losing exceptions. Some usages from catch blocks
    were migrated here, when it seemed that they were intended to avoid checked
    exception handling, and did not really intend to also suppress IOExceptions.
  3. New method CloseableUtils.closeAndSuppressExceptions, which sends all
    exceptions to a "chomper" that consumes them. Nothing is thrown or returned.
    The behavior is slightly different: with this method, all exceptions are
    suppressed, not just IOExceptions. Calls that seemed like they had good
    reason to suppress exceptions were migrated here.
  4. Some calls were migrated to try-with-resources, in cases where it appeared
    that CloseQuietly was being used to avoid throwing an exception in a finally
    block.

🎵 You don't have to go home, but you can't stay here... 🎵

gianm added 5 commits August 6, 2020 02:40
These other methods include:

1) New method CloseableUtils.closeAndWrapExceptions, which wraps IOExceptions
   in RuntimeExceptions for callers that just want to avoid dealing with
   checked exceptions. Most usages were migrated to this method, because it
   looks like they were mainly attempts to avoid declaring a throws clause,
   and perhaps were unintentionally suppressing IOExceptions.
2) New method CloseableUtils.closeInCatch, designed to properly close something
   in a catch block without losing exceptions. Some usages from catch blocks
   were migrated here, when it seemed that they were intended to avoid checked
   exception handling, and did not really intend to also suppress IOExceptions.
3) New method CloseableUtils.closeAndSuppressExceptions, which sends all
   exceptions to a "chomper" that consumes them. Nothing is thrown or returned.
   The behavior is slightly different: with this method, _all_ exceptions are
   suppressed, not just IOExceptions. Calls that seemed like they had good
   reason to suppress exceptions were migrated here.
4) Some calls were migrated to try-with-resources, in cases where it appeared
   that CloseQuietly was being used to avoid throwing an exception in a finally
   block.

🎵 You don't have to go home, but you can't stay here... 🎵
Copy link
Copy Markdown
Member

@clintropolis clintropolis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still working on review of this, a bit more than half done. I need to think harder about the places which were previously eating an IOException that will now have to deal with a RuntimeException, specifically the http client and json parser stuff, and also the low level segment mapping stuffs.

Closer closer = Closer.create();
closer.registerAll(cursors);
CloseQuietly.close(closer);
CloseableUtils.closeAndWrapExceptions(closer);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the usage of this method, it appears that it is expected to eat the exceptions:

      catch (Throwable t) {
        closeAllCursors(sequenceCursors);
        cancellationGizmo.cancel(t);
        out.offer(ResultBatch.TERMINAL);
      }

so, I think this method should be using closeAndSuppressExceptions. I guess callers could also be re-arranged to do the things that absolutely need done before closing the cursors, however, nothing would directly catch an exception thrown here, so it is sort of like screaming into the void, so I think we should just suppress them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it would be better to suppress IOException. We shouldn't throw IOException immediately especially at Line 650 since the terminal resultBatch must be added to the outputQueue after closing all cursors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change it to CloseableUtils.closeAndSuppressExceptions

}
}
CloseQuietly.close(columnarMultiInts);
CloseableUtils.closeAndWrapExceptions(columnarMultiInts);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I know this is a test and probably doesn't really matter, but it seems like mapper.close should get called probably. Should this be using closeAndSuppressExceptions to ensure that it gets closed, or make them both part of a closer that gets passed into this method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put them both in a single CloseableUtils.closeAll

}
}
CloseQuietly.close(columnarMultiInts);
CloseableUtils.closeAndWrapExceptions(columnarMultiInts);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment about closing mapper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same solution

}
catch (Exception ex) {
CloseQuietly.close(closer);
return Optional.empty();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was using CloseQuietly to eat exceptions, although probably not well enough since it was only eating IOExceptions. I think it should probably be using closeAndSuppressExceptions.

Per the javadocs of acquireReferences:

...
   * IMPORTANT NOTE: to fulfill the contract of this method, implementors must return a closeable to indicate that the
   * reference can be acquired, even if there is nothing to close. Implementors should avoid allowing this method or the
   * {@link Closeable} it creates to throw exceptions.
   *
   * For callers: if this method returns non-empty, IT MUST BE CLOSED, else reference counts can potentially leak.
...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, changed this to suppress.

Closer closer = Closer.create();
closer.registerAll(cursors);
CloseQuietly.close(closer);
CloseableUtils.closeAndWrapExceptions(closer);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it would be better to suppress IOException. We shouldn't throw IOException immediately especially at Line 650 since the terminal resultBatch must be added to the outputQueue after closing all cursors.


// Second exception
Assert.assertEquals(1, e.getSuppressed().length);
Assert.assertThat(e.getSuppressed()[0], CoreMatchers.instanceOf(RuntimeException.class));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why not IllegalArgumentException?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, that's a better test. I changed it.


// Second exception
Assert.assertEquals(1, e.getSuppressed().length);
Assert.assertThat(e.getSuppressed()[0], CoreMatchers.instanceOf(RuntimeException.class));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also changed this.

catch (Exception ex) {
CloseQuietly.close(resultSupplier);
throw ex;
catch (Throwable e) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why catching Throwables vs catching only Exceptions? It doesn't seem useful to do anything when you see any Error here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I can't think of a reason. Changed it back to Exception.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it more, I guess the reason is that we always want to close stuff, even if some weird Throwable got thrown. It's making it behave like a finally or like a try-with-resources. I changed it back to Throwable. Let me know if you disagree.

public void close()
{
CloseQuietly.close(closer);
CloseableUtils.closeAndWrapExceptions(closer);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you can modify the signature of this method to throw IOExceptoin, but this should also be fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The superclass won't allow it. (It overrides close and declares it to throw nothing.) I guess I could change the superclass, but I didn't want to go that far.

protected void loadBuffer(int bufferNum)
{
CloseQuietly.close(holder);
CloseableUtils.closeAndWrapExceptions(holder);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine because ResourceHolder.close() doesn't throw IOException, but this code seems confusing to me since, until looking at the code closely, I wasn't sure if it's fine to not execute the below lines when this line throws an exception. How about adding a comment here, such as "this line should never throw any checked exceptions"?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment for other places where close ResourceHolders.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize it didn't throw IOException. So actually, I could just remove the CloseableUtils call completely here, and replace it with holder.close(). I'll do that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did that, along with the other block layout column suppliers.


if (prevVal instanceof Closeable) {
CloseQuietly.close((Closeable) prevVal);
CloseableUtils.closeAndWrapExceptions((Closeable) prevVal);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the behavior, but any callers of fromIterableVersionOne() doesn't seem creating GenericIndexed from Closeable types. How about removing this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's ok with you, I'd prefer to leave it in for this patch, because I didn't want to go through and make sure it's always ok to skip closing.

@stale
Copy link
Copy Markdown

stale Bot commented Nov 5, 2020

This pull request has been marked as stale due to 60 days of inactivity. It will be closed in 4 weeks if no further activity occurs. If you think that's incorrect or this pull request should instead be reviewed, please simply write any comment. Even if closed, you can still revive the PR at any time or discuss it on the dev@druid.apache.org list. Thank you for your contributions.

@stale stale Bot added the stale label Nov 5, 2020
@stale
Copy link
Copy Markdown

stale Bot commented Dec 25, 2020

This pull request/issue has been closed due to lack of activity. If you think that is incorrect, or the pull request requires review, you can revive the PR at any time.

@stale stale Bot closed this Dec 25, 2020
@gianm
Copy link
Copy Markdown
Contributor Author

gianm commented Jan 8, 2021

nice try, stalebot

@gianm gianm reopened this Jan 8, 2021
@stale
Copy link
Copy Markdown

stale Bot commented Jan 8, 2021

This issue is no longer marked as stale.

@stale
Copy link
Copy Markdown

stale Bot commented Jan 8, 2021

This pull request/issue is no longer marked as stale.

@stale stale Bot removed the stale label Jan 8, 2021
@gianm
Copy link
Copy Markdown
Contributor Author

gianm commented Jan 8, 2021

Fixed up some merge conflicts.

@gianm
Copy link
Copy Markdown
Contributor Author

gianm commented Mar 13, 2021

Fixed up some more merge conflicts.

Copy link
Copy Markdown
Member

@clintropolis clintropolis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤘

* Like {@link Closeable#close()} but sends any exceptions to the provided Consumer, and then throws them away.
*
* If the Consumer throws an exception, that exception is thrown by this method. So if your intent is to chomp
* exceptions, you should avoid writing a Consumer that might thrown an exception.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* exceptions, you should avoid writing a Consumer that might thrown an exception.
* exceptions, you should avoid writing a Consumer that might throw an exception.

@gianm
Copy link
Copy Markdown
Contributor Author

gianm commented Oct 22, 2021

Fixed up some more merge conflicts.

@clintropolis clintropolis merged commit 98ecbb2 into apache:master Oct 24, 2021
@abhishekagarwal87 abhishekagarwal87 added this to the 0.23.0 milestone May 11, 2022
@gianm gianm deleted the fix-closequietlys branch September 23, 2022 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants