-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
WIP: Dbsync performance improvements #8496
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
Changes from 15 commits
2b78ac7
25888f4
90e0969
fbaf907
e608b1f
a2dd44e
04842c0
30a12f9
4251dc0
6fc9ecb
51aa6a6
cf209ee
a35b4b0
033a57e
e7b8219
e73f8bc
d8ba668
c1f74a5
08f243a
4b936a3
bf51775
d0e7237
bd0fc17
391a274
311f81b
8010a47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,4 +53,12 @@ public interface TaskExecutor { | |
| * Creates a new task throttler, and registers it so that it gets properly shutdown. | ||
| */ | ||
| DelayTaskThrottler createThrottler(int delay); | ||
|
|
||
| /** | ||
| * 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 | ||
| */ | ||
| void runInFXThread(Runnable runnable); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck> reported by reviewdog 🐶 |
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,8 @@ | |||||||||||||||
| import java.util.Set; | ||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||
|
|
||||||||||||||||
| import org.jabref.gui.util.BackgroundTask; | ||||||||||||||||
| import org.jabref.gui.util.TaskExecutor; | ||||||||||||||||
| import org.jabref.logic.citationkeypattern.GlobalCitationKeyPattern; | ||||||||||||||||
| import org.jabref.logic.exporter.BibDatabaseWriter; | ||||||||||||||||
| import org.jabref.logic.exporter.MetaDataSerializer; | ||||||||||||||||
|
|
@@ -55,9 +57,10 @@ public class DBMSSynchronizer implements DatabaseSynchronizer { | |||||||||||||||
| private final GlobalCitationKeyPattern globalCiteKeyPattern; | ||||||||||||||||
| private final FileUpdateMonitor fileMonitor; | ||||||||||||||||
| private Optional<BibEntry> lastEntryChanged; | ||||||||||||||||
| private final TaskExecutor taskExecutor; | ||||||||||||||||
|
|
||||||||||||||||
| public DBMSSynchronizer(BibDatabaseContext bibDatabaseContext, Character keywordSeparator, | ||||||||||||||||
| GlobalCitationKeyPattern globalCiteKeyPattern, FileUpdateMonitor fileMonitor) { | ||||||||||||||||
| GlobalCitationKeyPattern globalCiteKeyPattern, FileUpdateMonitor fileMonitor, TaskExecutor taskExecutor) { | ||||||||||||||||
| this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); | ||||||||||||||||
| this.bibDatabase = bibDatabaseContext.getDatabase(); | ||||||||||||||||
| this.metaData = bibDatabaseContext.getMetaData(); | ||||||||||||||||
|
|
@@ -66,6 +69,7 @@ public DBMSSynchronizer(BibDatabaseContext bibDatabaseContext, Character keyword | |||||||||||||||
| this.keywordSeparator = keywordSeparator; | ||||||||||||||||
| this.globalCiteKeyPattern = Objects.requireNonNull(globalCiteKeyPattern); | ||||||||||||||||
| this.lastEntryChanged = Optional.empty(); | ||||||||||||||||
| this.taskExecutor = taskExecutor; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
|
|
@@ -98,10 +102,12 @@ public void listen(FieldChangedEvent event) { | |||||||||||||||
| // While synchronizing the local database (see synchronizeLocalDatabase() below), some EntriesEvents may be posted. | ||||||||||||||||
| // In this case DBSynchronizer should not try to update the bibEntry entry again (but it would not harm). | ||||||||||||||||
| if (isPresentLocalBibEntry(bibEntry) && isEventSourceAccepted(event) && checkCurrentConnection() && !event.isFilteredOut()) { | ||||||||||||||||
| synchronizeLocalMetaData(); | ||||||||||||||||
| pullWithLastEntry(); | ||||||||||||||||
| synchronizeSharedEntry(bibEntry); | ||||||||||||||||
| synchronizeLocalDatabase(); // Pull changes for the case that there were some | ||||||||||||||||
| BackgroundTask.wrap(() -> { | ||||||||||||||||
| synchronizeLocalMetaData(); | ||||||||||||||||
| pullWithLastEntry(); | ||||||||||||||||
| synchronizeSharedEntry(bibEntry); | ||||||||||||||||
| synchronizeLocalDatabase(); // Pull changes for the case that there were some | ||||||||||||||||
| }).executeWith(taskExecutor); | ||||||||||||||||
| } else { | ||||||||||||||||
| // Set new BibEntry that has been changed last | ||||||||||||||||
| lastEntryChanged = Optional.of(bibEntry); | ||||||||||||||||
|
|
@@ -166,8 +172,12 @@ public void initializeDatabases() throws DatabaseNotSupportedException { | |||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| dbmsProcessor.startNotificationListener(this); | ||||||||||||||||
| synchronizeLocalMetaData(); | ||||||||||||||||
| synchronizeLocalDatabase(); | ||||||||||||||||
|
|
||||||||||||||||
| BackgroundTask.wrap(() -> { | ||||||||||||||||
| synchronizeLocalMetaData(); | ||||||||||||||||
| synchronizeLocalDatabase(); | ||||||||||||||||
| }).executeWith(taskExecutor); | ||||||||||||||||
|
|
||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
|
|
@@ -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(() -> | ||||||||||||||||
|
||||||||||||||||
| /** | |
| * 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

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.javadoc.RequireEmptyLineBeforeBlockTagGroupCheck> reported by reviewdog 🐶
Javadoc tag '@param' should be preceded with an empty line.