Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-2881: Mitigate cases of open iterators when cancelling queries. #2882

Merged
merged 1 commit into from
Dec 27, 2024

Conversation

Aklakan
Copy link
Contributor

@Aklakan Aklakan commented Dec 6, 2024

GitHub issue resolved #2881

Pull request Description:

  • Added a test case that should emit warnings without the fixes (the tests do not fail otherwise)
  • Adds exception handling to OpUnion and StageGeneratorGeneric, mainly to cope with concurrent QueryCancelledException.

  • Tests are included.
  • Commits have been squashed to remove intermediate development commit messages.
  • Key commit messages start with the issue number (GH-xxxx)

By submitting this pull request, I acknowledge that I am making a contribution to the Apache Software Foundation under the terms and conditions of the Contributor's Agreement.


See the Apache Jena "Contributing" guide.

@Aklakan Aklakan force-pushed the 2024-12-06-cancel-leak branch from 54440a4 to cf30474 Compare December 6, 2024 21:44
@@ -65,7 +86,7 @@ protected QueryIterator execute(BasicPattern pattern, ReorderTransformation reor
QueryIterPeek peek = QueryIterPeek.create(input, execCxt) ;
// And now use this one
input = peek ;
Binding b = peek.peek() ;
Binding b = closeOnException(peek::peek, peek) ;
Copy link
Member

Choose a reason for hiding this comment

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

It looks to me like QueryIterPeek should be updated to handle cancellations rather than putting execution costs into the main path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mhm, an exception during iter.peek() could close iter.

@Aklakan Aklakan force-pushed the 2024-12-06-cancel-leak branch 4 times, most recently from db77f93 to 9f1564a Compare December 7, 2024 12:08
try {
if ( ! hasNextBinding() )
return null ;
} catch (Exception e) {
Copy link
Contributor Author

@Aklakan Aklakan Dec 7, 2024

Choose a reason for hiding this comment

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

Perhaps having the try-catch block in StageGeneratorGeneric.execute is slightly better. Essentially, a method that returns a QueryIterator should close its own already allocated resources early in case of a failure. It feels not totally clean having peek() deal with an exception but not hasNextBinding / moveToNext.

The thing is, that even if an iterator's hasNextBinding / moveToNext raise an exception, then this iterator will under normal conditions be connected to the 'root' iterator. The query execution will catch an exception and close the root iterator which will propagate to its children and only then a check for dangling resources is done.

The problem is that StageGeneratorGeneric creates an QueryIterPeek, then calls peek which fails, and then the already registered iterator is left dangling - so its not really an issue of peek().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved the try-catch block back to StageGeneratorGeneric because based on my previous argument it would be the appropriate place.

@Aklakan Aklakan force-pushed the 2024-12-06-cancel-leak branch 4 times, most recently from 7221065 to e9293d1 Compare December 11, 2024 12:41
@Aklakan
Copy link
Contributor Author

Aklakan commented Dec 11, 2024

This actual proposed fix is complete for this PR. The test case would now also fail in case of open iterators. The number of threads created by the test case depends on the number of available cores. I am not sure whether the test case could be improved to produce the error condition more frequently - so far adding more cores increases the chance.

@Aklakan Aklakan force-pushed the 2024-12-06-cancel-leak branch from e9293d1 to e153b53 Compare December 13, 2024 22:28
Comment on lines +68 to +86
protected boolean hasNextBinding() {
for (;;) {
if (qIter != null) {
if (qIter.hasNext()) {
return true;
} else {
qIter.close();
qIter = null;
}
} else {
if (subOpIt.hasNext()) {
Op subOp = subOpIt.next();
qIter = QC.execute(subOp, binding, getExecContext());
} else {
return false;
}
}
}
}
Copy link
Contributor Author

@Aklakan Aklakan Dec 13, 2024

Choose a reason for hiding this comment

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

Making the nextStage() method of QueryIterUnion truly lazy removes the need for my previously proposed try-catch blocks: With this change, sub-iterators are only opened when needed, and if a creation fails then there are no previously opened resources that need to be cleaned up.

@Aklakan Aklakan force-pushed the 2024-12-06-cancel-leak branch 2 times, most recently from d6e4a2e to bc317ce Compare December 13, 2024 23:05
@Aklakan Aklakan force-pushed the 2024-12-06-cancel-leak branch from bc317ce to de0e047 Compare December 14, 2024 00:30
Aklakan added a commit to Aklakan/jena that referenced this pull request Dec 17, 2024
@afs afs merged commit 1617486 into apache:main Dec 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Open iterators when cancelling queries
2 participants