-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
WIP: Dbsync performance improvements #8496
base: main
Are you sure you want to change the base?
Conversation
* Kai-Franke-DLR-dbsync: fix architecture violation Handling of field change events as background task
@@ -221,7 +231,8 @@ public void synchronizeLocalDatabase() { | |||
|
|||
if (!entriesToInsertIntoLocalDatabase.isEmpty()) { | |||
// in case entries should be added into the local database, insert them | |||
bibDatabase.insertEntries(dbmsProcessor.getSharedEntries(entriesToInsertIntoLocalDatabase), EntriesEventSource.SHARED); | |||
taskExecutor.runInFXThread(() -> |
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 really necessary? I would say it's the job of the ui to make sure it handles notifications about new entries on the right thread, and not of the logic
. In fact, the logic shouldn't even know about the concept of fx-threads.
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.
Yes, while I agree with you that from an architectural standpoint this looks odd, the key problem is that if you insert anything in the bib database or modify a field, the GUI observables in the entry editor or maintable are called as well because they are bound to the bib database object.
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.
For this situation, we have the "forUi" helper
jabref/src/main/java/org/jabref/gui/util/BindingsHelper.java
Lines 165 to 170 in de114d5
/** | |
* Returns a wrapper around the given list that posts changes on the JavaFX thread. | |
*/ | |
public static <T> ObservableList<T> forUI(ObservableList<T> list) { | |
return new UiThreadList<>(list); | |
} |
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.
Yes, but I have encountered this before, this leads to a deadlock under certain conditions:
Comment that line out and connect to the shared SQL library in #8485
See here. I can only solve this by adding a timeout in the UI Thread list
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.
NOTE: applications should avoid flooding JavaFX with too many pending Runnables. Otherwise, the application may become unresponsive. Applications are encouraged to batch up multiple operations into fewer runLater calls. Additionally, long-running operations should be done on a background thread where possible, freeing up the JavaFX Application Thread for GUI operations.
That might be the reason. How many source changes does the database trigger?
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.
Nice analysis, thanks!
What I don't understand is why does the background thread pauses in the UiThreadList? Shouldn't it go on, sync the rest of the changes and finally give the synced entry list free?
Maybe it works if one adds the forUi
call at the last position here
entriesSorted = new SortedList<>(entriesFiltered); |
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.
the BackgroundThread only indirectly pauses in the UIList because
- When calling add() on the SynchronizedLIst, Background Thread acquires a mutex for the whole list.
- it's waiting for the CountDownLatch that never happens; this is why I tested to solve it with a timeout in the latch.
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> {
fireChange(change);
latch.countDown(); //never gets called, because we are stil in fireChange and bocked
});
try {
latch.await();
// boolean latched = latch.await(3L, TimeUnit.SECONDS);
// LOGGER.debug("Result from latch {}", latched); // if this is false, it was canceled by the timeout
} catch (InterruptedException e) {
LOGGER.error("Error while running on JavaFX thread", e);
} finally {
latch.countDown();
}
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.
@tobiasdiez I could solve it for the table, but I discovered that this also affects the GroupNodeViewModel, where a listener is registered. this can happend when switching lbraries now.
May also need to adapt it for the FilteredList...
entriesList = databaseContext.getDatabase().getEntries();
entriesList.addListener(this::onDatabaseChanged);
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.
Mhh, maybe it's a good idea to step back and try to come up with a general solution to these problems?
Off the top of my head, we have two ways to handle background threads that result in updates to the UI:
- Background threads are only allowed to calculate things, but never change any of the data model classes directly.
- Background threads are allowed to change data models, but at every point where the data model classes are bind to the UI we make sure that the updates are happening on the JavaFX thread (that essentially only concerns the main table, group view and entry editor).
I think both ways have advantages and disadvantages. I don't have a strong preference right now, do you? But I do think it would make sense to decide which approach we take and then do changes accordingly.
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.
From an architectural standpoint, updating the model should not have direct influences of the UI. The UI should take care that it is updating things in the right thread. This is more or less our current architecture. (2nd approach).
The key problem is more that we have SynchronizedList which is bound to the UI.
It has a mutex on all methods which will be held when elements are inserted. So while Elements are inserted a lock is held. Firing Changes on the FX Thread will always access the underlying list.
I will see if this work with the forUI for groups as well
This need some more thinking how to solve blocking threads when the EventBus fires. Therefore I close this for the moment and reopen when I have more insights |
* upstream/main: (387 commits) Show a warning in the merge dialog when authors are the same but formatted differently (#9088) Fix subdatabase from aux on cli (#9117) Visual improvements to LinkedFilesEditor (#9114) SLR Remove "last-search-date" (#9116) Hide diffs when one of the field values is blank a.k.a no conflict (#9110) Squashed 'buildres/csl/csl-locales/' changes from e637746677..b2afeb4d87 Squashed 'buildres/csl/csl-styles/' changes from c750b6e..8d69f16 Fix title case capitalization after en-dash characters (#9102) Update journal abbrev list (#9109) Fix CSL rendering in case of article (#8607) [WIP][GSOC22] - C - Improve the external changes resolver dialog (#9021) Bump jsoup from 1.15.1 to 1.15.3 (#9103) Bump checkstyle from 10.3.2 to 10.3.3 (#9104) Bump postgresql from 42.4.2 to 42.5.0 (#9105) Bump unirest-java from 3.13.10 to 3.13.11 (#9106) Include check for TimeStamp (#9089) Close OO connection on JabRef exit (#9076) Bump slf4j-tinylog from 2.4.1 to 2.5.0 (#9085) Bump bcprov-jdk18on from 1.71 to 1.71.1 (#9079) Bump tinylog-impl from 2.4.1 to 2.5.0 (#9086) ... # Conflicts: # src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java # src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java # src/main/java/org/jabref/logic/shared/DBMSSynchronizer.java
Platform.runLater(()-> { | ||
entriesList = databaseContext.getDatabase().getEntries(); | ||
entriesList.addListener(this::onDatabaseChanged); | ||
}); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck> reported by reviewdog 🐶
There is more than 1 empty line after this line.
fireChange(change); | ||
latch.countDown(); | ||
try { | ||
fireChange(change); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck> reported by reviewdog 🐶
Blank line at end of block should be removed
try { | ||
fireChange(change); | ||
|
||
} |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.blocks.RightCurlyCheck> reported by reviewdog 🐶
'}' at column 17 should be on the same line as the next part of a multi-block statement (one that directly contains multiple blocks: if/else-if/else, do/while or try/catch/finally).
|
||
} | ||
finally | ||
{ |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck> reported by reviewdog 🐶
'{' at column 17 should be on the previous line.
/** | ||
* If {@link TaskExecutor} is an instance of {@link DefaultTaskExecutor} then it is equivalent of calling {@link DefaultTaskExecutor#runInJavaFXThread(Runnable)} | ||
* otherwise it is executed on the same thread as the caller. This is extremely useful for testing code that does calls to the FXThread | ||
* @param runnable The runnable to execute |
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.
* otherwise it is executed on the same thread as the caller. This is extremely useful for testing code that does calls to the FXThread | ||
* @param runnable The runnable to execute | ||
*/ | ||
void runInFXThread(Runnable runnable); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck> reported by reviewdog 🐶
Blank line at end of block should be removed
if (idVersionEntry.getValue() > localEntry.getSharedBibEntryData().getVersion()) { | ||
Optional<BibEntry> sharedEntry = dbmsProcessor.getSharedEntry(idVersionEntry.getKey()); | ||
if (sharedEntry.isPresent()) { | ||
// update fields | ||
localEntry.setType(sharedEntry.get().getType(), EntriesEventSource.SHARED); | ||
localEntry.getSharedBibEntryData() | ||
.setVersion(sharedEntry.get().getSharedBibEntryData().getVersion()); | ||
|
||
|
||
taskExecutor.runInFXThread(()-> { |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck> reported by reviewdog 🐶
There is more than 1 empty line after this line.
if (idVersionEntry.getValue() > localEntry.getSharedBibEntryData().getVersion()) { | ||
Optional<BibEntry> sharedEntry = dbmsProcessor.getSharedEntry(idVersionEntry.getKey()); | ||
if (sharedEntry.isPresent()) { | ||
// update fields | ||
localEntry.setType(sharedEntry.get().getType(), EntriesEventSource.SHARED); | ||
localEntry.getSharedBibEntryData() | ||
.setVersion(sharedEntry.get().getSharedBibEntryData().getVersion()); | ||
|
||
|
||
taskExecutor.runInFXThread(()-> { |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck> reported by reviewdog 🐶
There is more than 1 empty line after this line.
if (!remoteEntryMatchingOneLocalEntryFound) { | ||
entriesToInsertIntoLocalDatabase.add(idVersionEntry.getKey()); | ||
} | ||
} | ||
|
||
if (!entriesToInsertIntoLocalDatabase.isEmpty()) { | ||
|
||
taskExecutor.runInFXThread( () -> bibDatabase.insertEntries(dbmsProcessor.getSharedEntries(entriesToInsertIntoLocalDatabase), EntriesEventSource.SHARED)); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck> reported by reviewdog 🐶
'(' is followed by whitespace.
if (!remoteEntryMatchingOneLocalEntryFound) { | ||
entriesToInsertIntoLocalDatabase.add(idVersionEntry.getKey()); | ||
} | ||
} | ||
|
||
// TODO: Update at once? or wrap whole method again in taskExecutor? |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck> reported by reviewdog 🐶
There is more than 1 empty line after this line.
if (!entriesToInsertIntoLocalDatabase.isEmpty()) { | ||
|
||
taskExecutor.runInFXThread( () -> bibDatabase.insertEntries(dbmsProcessor.partitionAndGetSharedEntries(entriesToInsertIntoLocalDatabase), EntriesEventSource.SHARED) ); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck> reported by reviewdog 🐶
'(' is followed by whitespace.
if (!entriesToInsertIntoLocalDatabase.isEmpty()) { | ||
|
||
taskExecutor.runInFXThread( () -> bibDatabase.insertEntries(dbmsProcessor.partitionAndGetSharedEntries(entriesToInsertIntoLocalDatabase), EntriesEventSource.SHARED) ); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck> reported by reviewdog 🐶
')' is preceded with whitespace.
if (!entriesToInsertIntoLocalDatabase.isEmpty()) { | ||
|
||
taskExecutor.runInFXThread( () -> bibDatabase.insertEntries(dbmsProcessor.partitionAndGetSharedEntries(entriesToInsertIntoLocalDatabase), EntriesEventSource.SHARED) ); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.SingleSpaceSeparatorCheck> reported by reviewdog 🐶
Use a single space to separate non-whitespace characters.
* upstream/main: Fix parsing of JabRef v5.7 study.yml files (#9124) Fix integrity check for tilde accents in author names (#9097) Rework the Define study parameters dialog (#9123) Fix parsing of save actions (#9122) Fix exception that occurs when saving name formatters (#9121) Refine code for BibEntry#replaceDownloadedFile (#9118)
* upstream/main: fix l10n Compile fix # Conflicts: # src/main/java/org/jabref/gui/slr/StudyDatabaseItem.java
remoteEntryMatchingOneLocalEntryFound = true; | ||
if (idVersionEntry.getValue() > localEntry.getSharedBibEntryData().getVersion()) { | ||
Optional<BibEntry> sharedEntry = dbmsProcessor.getSharedEntry(idVersionEntry.getKey()); | ||
remoteEntryMatchingOneLocalEntryFound = true; |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck> reported by reviewdog 🐶
There is more than 1 empty line after this line.
remoteEntryMatchingOneLocalEntryFound = true; | ||
|
||
|
||
if (idVersionEntry.getValue() > localEntry.getSharedBibEntryData().getVersion()) { |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck> reported by reviewdog 🐶
Blank line at start of block should be removed
BackgroundTask.wrap(() -> dbmsProcessor.partitionAndGetSharedEntries(entriesToInsertIntoLocalDatabase)) | ||
.onSuccess(entries-> { | ||
// in case entries should be added into the local database, insert them | ||
bibDatabase.insertEntries(entries, EntriesEventSource.SHARED); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck> reported by reviewdog 🐶
Blank line at end of block should be removed
// in case entries should be added into the local database, insert them | ||
bibDatabase.insertEntries(entries, EntriesEventSource.SHARED); | ||
|
||
}).executeWith(taskExecutor); |
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.
🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck> reported by reviewdog 🐶
Blank line at end of block should be removed
I could not push to the original one
Follow up to #8494
Introduced a dummy FXThread method for the CurrentTaskExecutor to fix the tests and added some synchronized to prevent errors with transactions. Need to check if we need this for other udpates as well
CHANGELOG.md
described in a way that is understandable for the average user (if applicable)