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

Fixes #127 - Avoid possible deadlock situations on updating GlazedLists #128

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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