Skip to content

Commit

Permalink
Fixes #127 - Avoid possible deadlock situations on updating GlazedLists
Browse files Browse the repository at this point in the history
Signed-off-by: Dirk Fauth <dirk.fauth@googlemail.com>
  • Loading branch information
fipro78 committed Nov 18, 2024
1 parent 0e5a1bd commit 1bd637c
Show file tree
Hide file tree
Showing 14 changed files with 476 additions and 359 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2013, 2020 Dirk Fauth and others.
* Copyright (c) 2013, 2024 Dirk Fauth and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -38,6 +38,7 @@
import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.DarkGroupByThemeExtension;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.DefaultGroupByThemeExtension;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.groupBy.GroupByConfigAttributes;
Expand Down Expand Up @@ -538,19 +539,20 @@ public void widgetSelected(SelectionEvent e) {
change.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
bodyLayerStack.getSortedList().getReadWriteLock().writeLock().lock();
try {
// deactivate
bodyLayerStack.getGlazedListsEventLayer().deactivate();
// clear
bodyLayerStack.getSortedList().clear();
// addall
bodyLayerStack.getSortedList().addAll(PersonService.getExtendedPersonsWithAddress(1000));
} finally {
bodyLayerStack.getSortedList().getReadWriteLock().writeLock().unlock();
// activate
bodyLayerStack.getGlazedListsEventLayer().activate();
}
GlazedListsLockHelper.performWriteOperation(
bodyLayerStack.getSortedList().getReadWriteLock(),
() -> {
// deactivate
bodyLayerStack.getGlazedListsEventLayer().deactivate();
// clear
bodyLayerStack.getSortedList().clear();
// addall
bodyLayerStack.getSortedList().addAll(PersonService.getExtendedPersonsWithAddress(1000));
},
() -> {
// activate
bodyLayerStack.getGlazedListsEventLayer().activate();
});
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016, 2023 Dirk Fauth and others.
* Copyright (c) 2016, 2024 Dirk Fauth and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -52,6 +52,7 @@
import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.data.command.GlazedListsRowInsertCommandHandler;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.data.command.GlazedListsRowObjectDeleteCommandHandler;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxFilterRowHeaderComposite;
Expand Down Expand Up @@ -576,36 +577,38 @@ public void widgetSelected(SelectionEvent e) {
address.setPostalCode(12345);
address.setCity("In the clouds");

bodyLayerStack.getEventList().getReadWriteLock().writeLock().lock();
try {
Person person = new Person(42, "Ralph", "Wiggum", Gender.MALE, false, new Date());
ExtendedPersonWithAddress entry = new ExtendedPersonWithAddress(person, address,
"0000", "The little Ralphy", 0,
new ArrayList<String>(), new ArrayList<String>());
bodyLayerStack.getEventList().add(entry);

person = new Person(42, "Clancy", "Wiggum", Gender.MALE, true, new Date());
entry = new ExtendedPersonWithAddress(person, address,
"XXXL", "It is Chief Wiggum", 0, new ArrayList<String>(), new ArrayList<String>());
bodyLayerStack.getEventList().add(entry);

person = new Person(42, "Sarah", "Wiggum", Gender.FEMALE, true, new Date());
entry = new ExtendedPersonWithAddress(person, address,
"mommy", "Little Ralphy's mother", 0,
new ArrayList<String>(), new ArrayList<String>());
bodyLayerStack.getEventList().add(entry);
} finally {
bodyLayerStack.getEventList().getReadWriteLock().writeLock().unlock();
// Inserting new objects could cause the creation of new
// GroupByObjects dependent on the GroupBy state. If
// additionally to the GroupBy state a sorting is applied on
// a column that contain a GroupBy summary value, the
// comparison and therefore the sorting is incorrect as the
// GroupBy summary value cannot be calculated. Therefore the
// sorting is re-applied after the insert operation to have
// a reliable sorting.
sortModel.refresh();
}
GlazedListsLockHelper.performWriteOperation(
bodyLayerStack.getEventList().getReadWriteLock(),
() -> {
Person person = new Person(42, "Ralph", "Wiggum", Gender.MALE, false, new Date());
ExtendedPersonWithAddress entry = new ExtendedPersonWithAddress(person, address,
"0000", "The little Ralphy", 0,
new ArrayList<String>(), new ArrayList<String>());
bodyLayerStack.getEventList().add(entry);

person = new Person(42, "Clancy", "Wiggum", Gender.MALE, true, new Date());
entry = new ExtendedPersonWithAddress(person, address,
"XXXL", "It is Chief Wiggum", 0, new ArrayList<String>(), new ArrayList<String>());
bodyLayerStack.getEventList().add(entry);

person = new Person(42, "Sarah", "Wiggum", Gender.FEMALE, true, new Date());
entry = new ExtendedPersonWithAddress(person, address,
"mommy", "Little Ralphy's mother", 0,
new ArrayList<String>(), new ArrayList<String>());
bodyLayerStack.getEventList().add(entry);
},
() -> {
// Inserting new objects could cause the creation of
// new GroupByObjects dependent on the GroupBy
// state. If additionally to the GroupBy state a
// sorting is applied on a column that contain a
// GroupBy summary value, the comparison and
// therefore the sorting is incorrect as the GroupBy
// summary value cannot be calculated. Therefore the
// sorting is re-applied after the insert operation
// to have a reliable sorting.
sortModel.refresh();
});
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import org.eclipse.nebula.widgets.nattable.export.image.config.DefaultImageExportBindings;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsLockHelper;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxFilterRowHeaderComposite;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.ComboBoxGlazedListsWithExcludeFilterStrategy;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.filterrow.FilterRowUtils;
Expand Down Expand Up @@ -643,29 +644,30 @@ public void widgetSelected(SelectionEvent e) {
replaceContentButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
bodyLayerStack.getSortedList().getReadWriteLock().writeLock().lock();
try {
// deactivate
bodyLayerStack.getGlazedListsEventLayer().deactivate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate();

// clear
bodyLayerStack.getSortedList().clear();

// addall
if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.compareAndSet(true, false)) {
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons);
} else {
_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.set(true);
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons);
// bodyLayerStack.getSortedList().addAll(PersonService.getPersonsWithAddress(200));
}
} finally {
bodyLayerStack.getSortedList().getReadWriteLock().writeLock().unlock();
// activate
bodyLayerStack.getGlazedListsEventLayer().activate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate();
}
GlazedListsLockHelper.performWriteOperation(
bodyLayerStack.getSortedList().getReadWriteLock(),
() -> {
// deactivate
bodyLayerStack.getGlazedListsEventLayer().deactivate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate();

// clear
bodyLayerStack.getSortedList().clear();

// addall
if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.compareAndSet(true, false)) {
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons);
} else {
_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.set(true);
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons);
// bodyLayerStack.getSortedList().addAll(PersonService.getPersonsWithAddress(200));
}
},
() -> {
// activate
bodyLayerStack.getGlazedListsEventLayer().activate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate();
});
}
});

Expand All @@ -674,27 +676,28 @@ public void widgetSelected(SelectionEvent e) {
reapplyContentButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
bodyLayerStack.getSortedList().getReadWriteLock().writeLock().lock();
try {
// deactivate
bodyLayerStack.getGlazedListsEventLayer().deactivate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate();

// clear
bodyLayerStack.getSortedList().clear();

// addall
if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.get()) {
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons);
} else {
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons);
}
} finally {
bodyLayerStack.getSortedList().getReadWriteLock().writeLock().unlock();
// activate
bodyLayerStack.getGlazedListsEventLayer().activate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate();
}
GlazedListsLockHelper.performWriteOperation(
bodyLayerStack.getSortedList().getReadWriteLock(),
() -> {
// deactivate
bodyLayerStack.getGlazedListsEventLayer().deactivate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().deactivate();

// clear
bodyLayerStack.getSortedList().clear();

// addall
if (_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersonsActive.get()) {
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.alternativePersons);
} else {
bodyLayerStack.getSortedList().addAll(_818_SortableAllFilterPerformanceColumnGroupExample.this.mixedPersons);
}
},
() -> {
// activate
bodyLayerStack.getGlazedListsEventLayer().activate();
columnHeaderLayerStack.getFilterRowComboBoxDataProvider().activate();
});
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*******************************************************************************
* Copyright (c) 2024 Dirk Fauth and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.glazedlists;

import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.odell.glazedlists.util.concurrent.Lock;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;

/**
* Helper class to perform write operations on a GlazedLists that uses
* {@link Lock#tryLock()} with a waiting time of 1 minute to reduce the risk of
* deadlocks or endless waiting.
*
* @since 2.5
*/
public final class GlazedListsLockHelper {

private static final Logger LOG = LoggerFactory.getLogger(GlazedListsLockHelper.class);

private GlazedListsLockHelper() {
// do nothing
}

/**
* Try to acquire the write lock for the given {@link Lock} using
* {@link Lock#tryLock()}. Waits for 1 minute to acquire the write lock, if
* not possible fire an {@link IllegalStateException}.
*
* @param writeLock
* The write {@link Lock}.
* @throws IllegalStateException
* if the write lock can not be acquired in 1 minute.
*/
public static void acquireWriteLock(Lock writeLock) {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis();
while ((end - start) < 60_000) {
try {
boolean success = writeLock.tryLock();

if (success) {
return;
}

Thread.sleep(50);
} catch (InterruptedException e) {
LOG.debug("thread interrupted while waiting for writeLock", e); //$NON-NLS-1$
}

end = System.currentTimeMillis();
}

throw new IllegalStateException("Failed to acquire the write lock!"); //$NON-NLS-1$
}

/**
* Performs a write operation on a GlazedLists that is wrapped by acquiring
* and releasing the write lock.
*
* @param lock
* The GlazedLists {@link Lock} object to get the write lock
* from.
* @param writeRunnable
* The {@link Runnable} that should be executed between obtaining
* the write lock and releasing it.
*/
public static void performWriteOperation(ReadWriteLock lock, Runnable writeRunnable) {
Lock writeLock = lock.writeLock();
try {
acquireWriteLock(writeLock);
writeRunnable.run();
} finally {
writeLock.unlock();
}
}

/**
* Performs a write operation on a GlazedLists that is wrapped by acquiring
* and releasing the write lock.
*
* @param lock
* The GlazedLists {@link Lock} object to get the write lock
* from.
* @param writeRunnable
* The {@link Runnable} that should be executed between obtaining
* the write lock and releasing it.
* @param finallyRunnable
* The {@link Runnable} that should be executed after the write
* lock is released.
*/
public static void performWriteOperation(ReadWriteLock lock, Runnable writeRunnable, Runnable finallyRunnable) {
Lock writeLock = lock.writeLock();
try {
acquireWriteLock(writeLock);
writeRunnable.run();
} finally {
writeLock.unlock();
finallyRunnable.run();
}
}

/**
* Performs a write operation on a GlazedLists that is wrapped by acquiring
* and releasing the write lock.
*
* @param lock
* The GlazedLists {@link Lock} object to get the write lock
* from.
* @param writeSupplier
* The {@link Supplier} that should be executed between obtaining
* the write lock and releasing it.
* @return The result of the {@link Supplier#get()} method.
*/
public static <T> T performWriteOperation(ReadWriteLock lock, Supplier<T> writeSupplier) {
Lock writeLock = lock.writeLock();
try {
acquireWriteLock(writeLock);
return writeSupplier.get();
} finally {
writeLock.unlock();
}
}
}
Loading

0 comments on commit 1bd637c

Please sign in to comment.