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
[NOID] Fixes #2283: Utilize KernelTransaction.setStatusDetails() in 4.4 #3383
base: 4.4
Are you sure you want to change the base?
Conversation
@AzuObs can you please take a look on it? |
Hello 👋 I'm very sorry that I've not been quick to review this. It's definitely on my TO DO list. The main reason is that I've been fairly strict about prioritising security issues reported to us privately, and about the triggers work. I will try to review this soon, but please let me know if it's urgent in any way and I will review sooner. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally speaking I think the approach should work, and should add value to the lives of our users. So, kudos on the great start 😁
As usual I cannot let you get away scot-free. I left a few small nitpicks.
checkStatusDetails(db, query, params, null); | ||
} | ||
public static void checkStatusDetails(GraphDatabaseService db, String query, Map<String, Object> params, String startQuery) { | ||
String finalStartQuery = startQuery == null ? query : startQuery; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant to be String final startQuery = ...
?
String finalStartQuery = startQuery == null ? query : startQuery; | ||
|
||
ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
final Future<String> future = executor.submit(() -> db.executeTransactionally(query, params, Result::resultAsString)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem you will find here is that these tests will be flaky. Because the query starts executing right away, and could complete before the assertEventually on the next line begins, which means the assertEventually waits for something which never happens.
At least I believe that's how it works, but I'd be happy to be proven wrong.
What might be worth exploring is kicking off the assertEventually before we execute the query. If you'd like to explore this, then I think it might require some further thinking because currently assertEventually is blocking whereas we'd like the query to overlap.
final Future<String> future = executor.submit(() -> db.executeTransactionally(query, params, Result::resultAsString)); | ||
|
||
assertEventually(() -> TestUtil.<String>singleResultFirstColumn(db, | ||
"SHOW TRANSACTIONS YIELD statusDetails, currentQuery where currentQuery STARTS WITH $startQuery RETURN statusDetails", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"SHOW TRANSACTIONS YIELD statusDetails, currentQuery where currentQuery STARTS WITH $startQuery RETURN statusDetails", | |
"SHOW TRANSACTIONS YIELD statusDetails, currentQuery WHERE currentQuery STARTS WITH $startQuery RETURN statusDetails", |
assertEventually(() -> TestUtil.<String>singleResultFirstColumn(db, | ||
"SHOW TRANSACTIONS YIELD statusDetails, currentQuery where currentQuery STARTS WITH $startQuery RETURN statusDetails", | ||
Map.of("startQuery", finalStartQuery)), | ||
StringUtils::isNotEmpty, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StringUtils::isNotEmpty
only checks that there is a statusDetails which is not empty. Would it be worth adding a more meaningful check?
@@ -25,6 +25,13 @@ | |||
* @since 23.02.16 | |||
*/ | |||
public class FormatUtils { | |||
|
|||
public static <T> String asListed(Map<String, T> map) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static <T> String asListed(Map<String, T> map) { | |
public static <T> String asHyphenSeparatedList(Map<String, T> map) { |
"asList" would also be fine if you prefer that 👍 "asListed" is however a bit ambiguous because it can also mean "as advertised".
return map.entrySet() | ||
.stream() | ||
.map(e -> "- " + e.getKey() + ": " + e.getValue()) | ||
.collect(Collectors.joining("\n")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could consider making this into a comma separated list as that is slightly more standard. Completely up to you, though.
acceptBatch(); | ||
} | ||
|
||
public void updateStatus(boolean updateCurrent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public void updateStatus(boolean updateCurrent) { | |
private void updateStatus(boolean updateCurrent) { |
public void updateStatus(boolean updateCurrent) { | ||
final Map<String, Object> statusMap = JsonUtil.convertToMap(this.progressInfo); | ||
if (updateCurrent) { | ||
setKernelStatusMap(tx, true, statusMap); | ||
} | ||
setKernelStatusMap(tx, progressInfo.nodes + progressInfo.relationships + progressInfo.properties, statusMap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function is a little bit confusing, because it's not obvious at first why we have an if statement on L131 if we end up always calling setKernelStatus on L133.
I would instead recommend creating two separate functions:
- updateStatus
- updateStatusPeriodically
Then, from the "update" function we can call updateStatus. And from the "nextRow" function we can call updateStatusPeriodically.
@@ -245,6 +247,10 @@ private void handleNode(Deque<Map<String, Object>> stack, Node node, boolean sim | |||
} | |||
|
|||
if (!elementMap.isEmpty()) { | |||
final int counter = stack.size(); | |||
final Map<String, Object> statusMap = map("curr. element", counter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this map also be "map(records:, counter)" for consistency? Or is an element not the same thing as a record in this context?
@@ -103,6 +104,8 @@ public Stream<RundownResult> commit(@Name("statement") String statement, @Name(v | |||
return 0L; | |||
} | |||
}), commitErrors, failedCommits, 0L); | |||
setKernelStatusMap(tx, true, | |||
Map.of("successes", batches.get() - failedBatches.get(), "errors", failedBatches.get())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map.of("successes", batches.get() - failedBatches.get(), "errors", failedBatches.get())); | |
Map.of("committed", batches.get() - failedBatches.get(), "failed", failedBatches.get())); |
This seems more in line with our existing documentation https://neo4j.com/docs/apoc/current/overview/apoc.periodic/apoc.periodic.iterate/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment for the other instances of this happening in this file.
dbca89d
to
2630f3c
Compare
Related to #3097 (which fails because created from a fork)
Fixes #2283
Added KernelTransaction.setStatusDetails(..) every 1000 rows.
I don't added
@Context public KernelTransaction kernelTx
due to #2265 (comment).I used
final KernelTransaction ktx = ((InternalTransaction) tx).kernelTransaction();
instead.