8240690: Race condition between EDT and BasicDirectoryModel.FilesLoader.run0()

Reviewed-by: psadhukhan
This commit is contained in:
Sergey Bylokhov 2020-03-25 16:48:35 -07:00
parent 40e667c174
commit 3399842d7e
5 changed files with 344 additions and 120 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,30 +22,83 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.java.swing.plaf.gtk;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.awt.AWTKeyStroke;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.Vector;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.filechooser.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.accessibility.AccessibleContext;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListSelectionModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicDirectoryModel;
import javax.swing.table.*;
import javax.accessibility.*;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import sun.swing.SwingUtilities2;
import sun.swing.plaf.synth.*;
import sun.swing.FilePane;
import sun.awt.shell.ShellFolder;
import sun.swing.FilePane;
import sun.swing.SwingUtilities2;
import sun.swing.plaf.synth.SynthFileChooserUI;
/**
* GTK FileChooserUI.
@ -914,21 +967,18 @@ class GTKFileChooserUI extends SynthFileChooserUI {
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
private class GTKDirectoryModel extends BasicDirectoryModel {
FileSystemView fsv;
private Comparator<File> fileComparator = new Comparator<File>() {
public int compare(File o, File o1) {
return fsv.getSystemDisplayName(o).compareTo(fsv.getSystemDisplayName(o1));
}
};
public GTKDirectoryModel() {
private final class GTKDirectoryModel extends BasicDirectoryModel {
private GTKDirectoryModel() {
super(getFileChooser());
}
protected void sort(Vector<? extends File> v) {
fsv = getFileChooser().getFileSystemView();
Collections.sort(v, fileComparator);
FileSystemView fsv = getFileChooser().getFileSystemView();
if (fsv == null) {
super.sort(v);
} else {
v.sort(Comparator.comparing(fsv::getSystemDisplayName));
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,35 +22,48 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.FileChooserUI;
import javax.accessibility.*;
import java.io.*;
import java.util.Vector;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.BorderLayout;
import java.awt.Window;
import java.awt.Dialog;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.*;
import java.beans.JavaBean;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.InputEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.BeanProperty;
import java.beans.PropertyChangeListener;
import java.beans.JavaBean;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Vector;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.swing.event.EventListenerList;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import javax.swing.plaf.FileChooserUI;
/**
* <code>JFileChooser</code> provides a simple mechanism for the user to
@ -1610,20 +1623,23 @@ public class JFileChooser extends JComponent implements Accessible {
public boolean isTraversable(File f) {
Boolean traversable = null;
if (f != null) {
if (getFileView() != null) {
traversable = getFileView().isTraversable(f);
FileView fileView = getFileView();
if (fileView != null) {
traversable = fileView.isTraversable(f);
}
FileView uiFileView = getUI().getFileView(this);
if (traversable == null && uiFileView != null) {
traversable = uiFileView.isTraversable(f);
FileChooserUI ui = getUI();
if (traversable == null && ui != null) {
FileView uiFileView = ui.getFileView(this);
if (uiFileView != null) {
traversable = uiFileView.isTraversable(f);
}
}
if (traversable == null) {
traversable = getFileSystemView().isTraversable(f);
FileSystemView fileSystemView = getFileSystemView();
if (traversable == null && fileSystemView != null) {
traversable = fileSystemView.isTraversable(f);
}
}
return (traversable != null && traversable.booleanValue());
return traversable != null && traversable;
}
/**
@ -1633,11 +1649,8 @@ public class JFileChooser extends JComponent implements Accessible {
* @see FileFilter#accept
*/
public boolean accept(File f) {
boolean shown = true;
if(f != null && fileFilter != null) {
shown = fileFilter.accept(f);
}
return shown;
FileFilter filter = fileFilter;
return f == null || filter == null || filter.accept(f);
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,11 +25,6 @@
package javax.swing.plaf.basic;
import sun.awt.shell.ShellFolder;
import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.filechooser.FileSystemView;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
@ -37,6 +32,15 @@ import java.io.File;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.AbstractListModel;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.filechooser.FileSystemView;
import sun.awt.shell.ShellFolder;
/**
* Basic implementation of a file list.
@ -46,13 +50,13 @@ import java.util.concurrent.Callable;
@SuppressWarnings("serial") // Superclass is not serializable across versions
public class BasicDirectoryModel extends AbstractListModel<Object> implements PropertyChangeListener {
private JFileChooser filechooser = null;
private final JFileChooser filechooser;
// PENDING(jeff) pick the size more sensibly
private Vector<File> fileCache = new Vector<File>(50);
private final Vector<File> fileCache = new Vector<File>(50);
private FilesLoader filesLoader = null;
private Vector<File> files = null;
private Vector<File> directories = null;
private int fetchID = 0;
private final AtomicInteger fetchID = new AtomicInteger();
private PropertyChangeSupport changeSupport;
@ -157,9 +161,9 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
filesLoader.cancelRunnables();
}
setBusy(true, ++fetchID);
filesLoader = new FilesLoader(currentDirectory, fetchID);
int fid = fetchID.incrementAndGet();
setBusy(true, fid);
filesLoader = new FilesLoader(currentDirectory, fid);
}
/**
@ -260,18 +264,24 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
}
class FilesLoader implements Runnable {
File currentDirectory = null;
int fid;
Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10);
final Thread loadThread;
private final class FilesLoader implements Runnable {
private final FileSystemView fileSystemView;
private final boolean useFileHiding;
private final boolean fileSelectionEnabled;
private final int fid;
private final File currentDirectory;
private volatile DoChangeContents runnable;
private final Thread loadThread;
public FilesLoader(File currentDirectory, int fid) {
private FilesLoader(File currentDirectory, int fid) {
this.currentDirectory = currentDirectory;
this.fid = fid;
fileSystemView = filechooser.getFileSystemView();
useFileHiding = filechooser.isFileHidingEnabled();
fileSelectionEnabled = filechooser.isFileSelectionEnabled();
String name = "Basic L&F File Loading Thread";
this.loadThread = new Thread(null, this, name, 0, false);
this.loadThread.start();
loadThread = new Thread(null, this, name, 0, false);
loadThread.start();
}
@Override
@ -280,14 +290,14 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
setBusy(false, fid);
}
public void run0() {
FileSystemView fileSystem = filechooser.getFileSystemView();
private void run0() {
FileSystemView fileSystem = fileSystemView;
if (loadThread.isInterrupted()) {
return;
}
File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
File[] list = fileSystem.getFiles(currentDirectory, useFileHiding);
if (loadThread.isInterrupted()) {
return;
@ -305,7 +315,7 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
if (isTraversable) {
newFileCache.addElement(file);
} else if (filechooser.isFileSelectionEnabled()) {
} else if (fileSelectionEnabled) {
newFiles.addElement(file);
}
@ -323,7 +333,7 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
// To avoid loads of synchronizations with Invoker and improve performance we
// execute the whole block on the COM thread
DoChangeContents doChangeContents = ShellFolder.invoke(new Callable<DoChangeContents>() {
runnable = ShellFolder.invoke(new Callable<DoChangeContents>() {
public DoChangeContents call() {
int newSize = newFileCache.size();
int oldSize = fileCache.size();
@ -372,7 +382,7 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
}
if (!fileCache.equals(newFileCache)) {
if (loadThread.isInterrupted()) {
cancelRunnables(runnables);
cancelRunnables();
}
return new DoChangeContents(newFileCache, 0, fileCache, 0, fid);
}
@ -380,22 +390,16 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
}
});
if (doChangeContents != null) {
runnables.addElement(doChangeContents);
SwingUtilities.invokeLater(doChangeContents);
if (runnable != null && !loadThread.isInterrupted()) {
SwingUtilities.invokeLater(runnable);
}
}
public void cancelRunnables(Vector<DoChangeContents> runnables) {
for (DoChangeContents runnable : runnables) {
private void cancelRunnables() {
if (runnable != null) {
runnable.cancel();
}
}
public void cancelRunnables() {
cancelRunnables(runnables);
}
}
@ -486,7 +490,7 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
* thread in order to load the contents of a directory.
*/
private synchronized void setBusy(final boolean busy, int fid) {
if (fid == fetchID) {
if (fid == fetchID.get()) {
boolean oldValue = this.busy;
this.busy = busy;
@ -501,15 +505,16 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
}
class DoChangeContents implements Runnable {
private List<File> addFiles;
private List<File> remFiles;
private final class DoChangeContents implements Runnable {
private final List<File> addFiles;
private final List<File> remFiles;
private boolean doFire = true;
private int fid;
private final int fid;
private int addStart = 0;
private int remStart = 0;
public DoChangeContents(List<File> addFiles, int addStart, List<File> remFiles, int remStart, int fid) {
DoChangeContents(List<File> addFiles, int addStart, List<File> remFiles,
int remStart, int fid) {
this.addFiles = addFiles;
this.addStart = addStart;
this.remFiles = remFiles;
@ -518,11 +523,11 @@ public class BasicDirectoryModel extends AbstractListModel<Object> implements Pr
}
synchronized void cancel() {
doFire = false;
doFire = false;
}
public synchronized void run() {
if (fetchID == fid && doFire) {
if (fetchID.get() == fid && doFire) {
int remSize = (remFiles == null) ? 0 : remFiles.size();
int addSize = (addFiles == null) ? 0 : addFiles.size();
synchronized(fileCache) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,31 +22,106 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.swing;
import java.awt.*;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.DefaultKeyboardFocusManager;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.plaf.basic.*;
import javax.swing.table.*;
import javax.swing.text.*;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ButtonGroup;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListSelectionModel;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.RowSorter;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.RowSorterListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.filechooser.FileSystemView;
import javax.swing.plaf.basic.BasicDirectoryModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.text.Position;
import sun.awt.AWTAccessor;
import sun.awt.AWTAccessor.MouseEventAccessor;
import sun.awt.shell.*;
import sun.awt.shell.ShellFolder;
import sun.awt.shell.ShellFolderColumnInfo;
/**
* <b>WARNING:</b> This class is an implementation detail and is only
@ -694,8 +769,8 @@ public class FilePane extends JPanel implements PropertyChangeListener {
@SuppressWarnings("serial") // JDK-implementation class
class DetailsTableModel extends AbstractTableModel implements ListDataListener {
JFileChooser chooser;
BasicDirectoryModel directoryModel;
private final JFileChooser chooser;
private final BasicDirectoryModel directoryModel;
ShellFolderColumnInfo[] columns;
int[] columnMap;
@ -806,7 +881,7 @@ public class FilePane extends JPanel implements PropertyChangeListener {
JOptionPane.showMessageDialog(chooser, MessageFormat.format(renameErrorFileExistsText,
oldFileName), renameErrorTitleText, JOptionPane.ERROR_MESSAGE);
} else {
if (FilePane.this.getModel().renameFile(f, f2)) {
if (directoryModel.renameFile(f, f2)) {
if (fsv.isParent(chooser.getCurrentDirectory(), f2)) {
// The setSelectedFile method produces a new setValueAt invocation while the JTable
// is editing. Postpone file selection to be sure that edit mode of the JTable
@ -849,7 +924,7 @@ public class FilePane extends JPanel implements PropertyChangeListener {
int i0 = e.getIndex0();
int i1 = e.getIndex1();
if (i0 == i1) {
File file = (File)getModel().getElementAt(i0);
File file = (File)directoryModel.getElementAt(i0);
if (file.equals(newFolderFile)) {
new DelayedSelectionUpdater(file);
newFolderFile = null;

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.EventQueue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.concurrent.TimeUnit;
import javax.swing.JFileChooser;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import static javax.swing.UIManager.getInstalledLookAndFeels;
/**
* @test
* @bug 4966171 8240690
* @key headful
* @summary Tests that JFileChooser can be serialized
*/
public final class bug4966171 {
public static void main(String[] args) throws Exception {
for (UIManager.LookAndFeelInfo laf : getInstalledLookAndFeels()) {
EventQueue.invokeAndWait(() -> setLookAndFeel(laf));
EventQueue.invokeAndWait(bug4966171::test);
}
}
private static void test() {
// Will run the test no more than 10 seconds per L&F
long endtime = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (System.nanoTime() < endtime) {
try {
var byteOut = new ByteArrayOutputStream();
try (var out = new ObjectOutputStream(byteOut)) {
out.writeObject(new JFileChooser());
}
var byteIn = new ByteArrayInputStream(byteOut.toByteArray());
try (var in = new ObjectInputStream(byteIn)) {
JFileChooser readFc = (JFileChooser) in.readObject();
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
private static void setLookAndFeel(UIManager.LookAndFeelInfo laf) {
try {
UIManager.setLookAndFeel(laf.getClassName());
} catch (UnsupportedLookAndFeelException ignored) {
System.out.println("Unsupported L&F: " + laf.getClassName());
} catch (ClassNotFoundException | InstantiationException
| IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}