6713352: Deadlock in JFileChooser with synchronized custom FileSystemView
authorrupashka
Fri, 15 May 2009 17:26:45 +0400
changeset 1231993a5f0fe2e0
parent 1230 af491a9b7c1d
child 1232 019908df0313
6713352: Deadlock in JFileChooser with synchronized custom FileSystemView
Reviewed-by: malenkov, peterz
src/share/classes/javax/swing/plaf/basic/BasicDirectoryModel.java
src/share/classes/sun/awt/shell/ShellFolder.java
src/windows/classes/sun/awt/shell/Win32ShellFolder2.java
src/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java
test/javax/swing/JFileChooser/6713352/bug6713352.java
     1.1 --- a/src/share/classes/javax/swing/plaf/basic/BasicDirectoryModel.java	Fri May 15 12:06:22 2009 +0400
     1.2 +++ b/src/share/classes/javax/swing/plaf/basic/BasicDirectoryModel.java	Fri May 15 17:26:45 2009 +0400
     1.3 @@ -230,51 +230,46 @@
     1.4          }
     1.5  
     1.6          public void run0() {
     1.7 -            DoChangeContents doChangeContents = ShellFolder.getInvoker().invoke(new Callable<DoChangeContents>() {
     1.8 -                public DoChangeContents call() throws Exception {
     1.9 -                    FileSystemView fileSystem = filechooser.getFileSystemView();
    1.10 +            FileSystemView fileSystem = filechooser.getFileSystemView();
    1.11  
    1.12 -                    File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
    1.13 +            File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
    1.14  
    1.15 -                    Vector<File> acceptsList = new Vector<File>();
    1.16 +            if (isInterrupted()) {
    1.17 +                return;
    1.18 +            }
    1.19  
    1.20 -                    if (isInterrupted()) {
    1.21 -                        return null;
    1.22 -                    }
    1.23 +            final Vector<File> newFileCache = new Vector<File>();
    1.24 +            Vector<File> newFiles = new Vector<File>();
    1.25  
    1.26 -                    // run through the file list, add directories and selectable files to fileCache
    1.27 -                    for (File file : list) {
    1.28 -                        if (filechooser.accept(file)) {
    1.29 -                            acceptsList.addElement(file);
    1.30 -                        }
    1.31 +            // run through the file list, add directories and selectable files to fileCache
    1.32 +            // Note that this block must be OUTSIDE of Invoker thread because of
    1.33 +            // deadlock possibility with custom synchronized FileSystemView
    1.34 +            for (File file : list) {
    1.35 +                if (filechooser.accept(file)) {
    1.36 +                    boolean isTraversable = filechooser.isTraversable(file);
    1.37 +
    1.38 +                    if (isTraversable) {
    1.39 +                        newFileCache.addElement(file);
    1.40 +                    } else if (filechooser.isFileSelectionEnabled()) {
    1.41 +                        newFiles.addElement(file);
    1.42                      }
    1.43  
    1.44                      if (isInterrupted()) {
    1.45 -                        return null;
    1.46 +                        return;
    1.47                      }
    1.48 +                }
    1.49 +            }
    1.50  
    1.51 -                    // First sort alphabetically by filename
    1.52 -                    sort(acceptsList);
    1.53 +            // First sort alphabetically by filename
    1.54 +            sort(newFileCache);
    1.55 +            sort(newFiles);
    1.56  
    1.57 -                    Vector<File> newDirectories = new Vector<File>(50);
    1.58 -                    Vector<File> newFiles = new Vector<File>();
    1.59 -                    // run through list grabbing directories in chunks of ten
    1.60 -                    for (int i = 0; i < acceptsList.size(); i++) {
    1.61 -                        File f = acceptsList.elementAt(i);
    1.62 -                        boolean isTraversable = filechooser.isTraversable(f);
    1.63 -                        if (isTraversable) {
    1.64 -                            newDirectories.addElement(f);
    1.65 -                        } else if (!isTraversable && filechooser.isFileSelectionEnabled()) {
    1.66 -                            newFiles.addElement(f);
    1.67 -                        }
    1.68 -                        if (isInterrupted()) {
    1.69 -                            return null;
    1.70 -                        }
    1.71 -                    }
    1.72 +            newFileCache.addAll(newFiles);
    1.73  
    1.74 -                    Vector<File> newFileCache = new Vector<File>(newDirectories);
    1.75 -                    newFileCache.addAll(newFiles);
    1.76 -
    1.77 +            // To avoid loads of synchronizations with Invoker and improve performance we
    1.78 +            // execute the whole block on the COM thread
    1.79 +            DoChangeContents doChangeContents = ShellFolder.getInvoker().invoke(new Callable<DoChangeContents>() {
    1.80 +                public DoChangeContents call() throws Exception {
    1.81                      int newSize = newFileCache.size();
    1.82                      int oldSize = fileCache.size();
    1.83  
     2.1 --- a/src/share/classes/sun/awt/shell/ShellFolder.java	Fri May 15 12:06:22 2009 +0400
     2.2 +++ b/src/share/classes/sun/awt/shell/ShellFolder.java	Fri May 15 17:26:45 2009 +0400
     2.3 @@ -274,45 +274,61 @@
     2.4  
     2.5      // Override File methods
     2.6  
     2.7 -    public static void sort(List<? extends File> files) {
     2.8 +    public static void sort(final List<? extends File> files) {
     2.9          if (files == null || files.size() <= 1) {
    2.10              return;
    2.11          }
    2.12  
    2.13 -        // Check that we can use the ShellFolder.sortChildren() method:
    2.14 -        //   1. All files have the same non-null parent
    2.15 -        //   2. All files is ShellFolders
    2.16 -        File commonParent = null;
    2.17 +        // To avoid loads of synchronizations with Invoker and improve performance we
    2.18 +        // synchronize the whole code of the sort method once
    2.19 +        getInvoker().invoke(new Callable<Void>() {
    2.20 +            public Void call() throws Exception {
    2.21 +                // Check that we can use the ShellFolder.sortChildren() method:
    2.22 +                //   1. All files have the same non-null parent
    2.23 +                //   2. All files is ShellFolders
    2.24 +                File commonParent = null;
    2.25  
    2.26 -        for (File file : files) {
    2.27 -            File parent = file.getParentFile();
    2.28 +                for (File file : files) {
    2.29 +                    File parent = file.getParentFile();
    2.30  
    2.31 -            if (parent == null || !(file instanceof ShellFolder)) {
    2.32 -                commonParent = null;
    2.33 +                    if (parent == null || !(file instanceof ShellFolder)) {
    2.34 +                        commonParent = null;
    2.35  
    2.36 -                break;
    2.37 +                        break;
    2.38 +                    }
    2.39 +
    2.40 +                    if (commonParent == null) {
    2.41 +                        commonParent = parent;
    2.42 +                    } else {
    2.43 +                        if (commonParent != parent && !commonParent.equals(parent)) {
    2.44 +                            commonParent = null;
    2.45 +
    2.46 +                            break;
    2.47 +                        }
    2.48 +                    }
    2.49 +                }
    2.50 +
    2.51 +                if (commonParent instanceof ShellFolder) {
    2.52 +                    ((ShellFolder) commonParent).sortChildren(files);
    2.53 +                } else {
    2.54 +                    Collections.sort(files, FILE_COMPARATOR);
    2.55 +                }
    2.56 +
    2.57 +                return null;
    2.58              }
    2.59 -
    2.60 -            if (commonParent == null) {
    2.61 -                commonParent = parent;
    2.62 -            } else {
    2.63 -                if (commonParent != parent && !commonParent.equals(parent)) {
    2.64 -                    commonParent = null;
    2.65 -
    2.66 -                    break;
    2.67 -                }
    2.68 -            }
    2.69 -        }
    2.70 -
    2.71 -        if (commonParent instanceof ShellFolder) {
    2.72 -            ((ShellFolder) commonParent).sortChildren(files);
    2.73 -        } else {
    2.74 -            Collections.sort(files, FILE_COMPARATOR);
    2.75 -        }
    2.76 +        });
    2.77      }
    2.78  
    2.79 -    public void sortChildren(List<? extends File> files) {
    2.80 -        Collections.sort(files, FILE_COMPARATOR);
    2.81 +    public void sortChildren(final List<? extends File> files) {
    2.82 +        // To avoid loads of synchronizations with Invoker and improve performance we
    2.83 +        // synchronize the whole code of the sort method once
    2.84 +        getInvoker().invoke(new Callable<Void>() {
    2.85 +            public Void call() throws Exception {
    2.86 +                Collections.sort(files, FILE_COMPARATOR);
    2.87 +
    2.88 +                return null;
    2.89 +            }
    2.90 +        });
    2.91      }
    2.92  
    2.93      public boolean isAbsolute() {
     3.1 --- a/src/windows/classes/sun/awt/shell/Win32ShellFolder2.java	Fri May 15 12:06:22 2009 +0400
     3.2 +++ b/src/windows/classes/sun/awt/shell/Win32ShellFolder2.java	Fri May 15 17:26:45 2009 +0400
     3.3 @@ -527,7 +527,7 @@
     3.4      /**
     3.5       * @return Whether this is a file system shell folder
     3.6       */
     3.7 -    public synchronized boolean isFileSystem() {
     3.8 +    public boolean isFileSystem() {
     3.9          if (cachedIsFileSystem == null) {
    3.10              cachedIsFileSystem = hasAttribute(ATTRIB_FILESYSTEM);
    3.11          }
    3.12 @@ -543,8 +543,8 @@
    3.13              public Boolean call() throws Exception {
    3.14                  // Caching at this point doesn't seem to be cost efficient
    3.15                  return (getAttributes0(getParentIShellFolder(),
    3.16 -                        getRelativePIDL(), attribute)
    3.17 -                        & attribute) != 0;
    3.18 +                    getRelativePIDL(), attribute)
    3.19 +                    & attribute) != 0;
    3.20              }
    3.21          });
    3.22      }
    3.23 @@ -761,7 +761,7 @@
    3.24      /**
    3.25       * @return Whether this shell folder is a link
    3.26       */
    3.27 -    public synchronized boolean isLink() {
    3.28 +    public boolean isLink() {
    3.29          if (cachedIsLink == null) {
    3.30              cachedIsLink = hasAttribute(ATTRIB_LINK);
    3.31          }
    3.32 @@ -1160,8 +1160,16 @@
    3.33      private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx);
    3.34  
    3.35  
    3.36 -    public void sortChildren(List<? extends File> files) {
    3.37 -        Collections.sort(files, new ColumnComparator(getIShellFolder(), 0));
    3.38 +    public void sortChildren(final List<? extends File> files) {
    3.39 +        // To avoid loads of synchronizations with Invoker and improve performance we
    3.40 +        // synchronize the whole code of the sort method once
    3.41 +        getInvoker().invoke(new Callable<Void>() {
    3.42 +            public Void call() throws Exception {
    3.43 +                Collections.sort(files, new ColumnComparator(getIShellFolder(), 0));
    3.44 +
    3.45 +                return null;
    3.46 +            }
    3.47 +        });
    3.48      }
    3.49  
    3.50      private static class ColumnComparator implements Comparator<File> {
     4.1 --- a/src/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java	Fri May 15 12:06:22 2009 +0400
     4.2 +++ b/src/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java	Fri May 15 17:26:45 2009 +0400
     4.3 @@ -478,21 +478,22 @@
     4.4  
     4.5          public <T> T invoke(Callable<T> task) {
     4.6              try {
     4.7 -                T result;
     4.8                  if (Thread.currentThread() == comThread) {
     4.9                      // if it's already called from the COM
    4.10                      // thread, we don't need to delegate the task
    4.11 -                    result = task.call();
    4.12 +                    return task.call();
    4.13                  } else {
    4.14 -                    Future<T> future = submit(task);
    4.15 -                    try {
    4.16 -                        result = future.get();
    4.17 -                    } catch (InterruptedException e) {
    4.18 -                        result = null;
    4.19 -                        future.cancel(true);
    4.20 +                    while (true) {
    4.21 +                        Future<T> future = submit(task);
    4.22 +
    4.23 +                        try {
    4.24 +                            return future.get();
    4.25 +                        } catch (InterruptedException e) {
    4.26 +                            // Repeat the attempt
    4.27 +                            future.cancel(true);
    4.28 +                        }
    4.29                      }
    4.30                  }
    4.31 -                return result;
    4.32              } catch (Exception e) {
    4.33                  Throwable cause = (e instanceof ExecutionException) ? e.getCause() : e;
    4.34                  if (cause instanceof RuntimeException) {
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/test/javax/swing/JFileChooser/6713352/bug6713352.java	Fri May 15 17:26:45 2009 +0400
     5.3 @@ -0,0 +1,99 @@
     5.4 +/*
     5.5 + * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
     5.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     5.7 + *
     5.8 + * This code is free software; you can redistribute it and/or modify it
     5.9 + * under the terms of the GNU General Public License version 2 only, as
    5.10 + * published by the Free Software Foundation.
    5.11 + *
    5.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
    5.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    5.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    5.15 + * version 2 for more details (a copy is included in the LICENSE file that
    5.16 + * accompanied this code).
    5.17 + *
    5.18 + * You should have received a copy of the GNU General Public License version
    5.19 + * 2 along with this work; if not, write to the Free Software Foundation,
    5.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    5.21 + *
    5.22 + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
    5.23 + * CA 95054 USA or visit www.sun.com if you need additional information or
    5.24 + * have any questions.
    5.25 + */
    5.26 +
    5.27 +/* @test
    5.28 +   @bug 6713352
    5.29 +   @summary Deadlock in JFileChooser with synchronized custom FileSystemView
    5.30 +   @author Pavel Porvatov
    5.31 +   @run main bug6713352
    5.32 +*/
    5.33 +
    5.34 +import sun.awt.shell.ShellFolder;
    5.35 +
    5.36 +import javax.swing.*;
    5.37 +import javax.swing.filechooser.FileSystemView;
    5.38 +import java.io.File;
    5.39 +import java.io.FileNotFoundException;
    5.40 +import java.io.IOException;
    5.41 +
    5.42 +public class bug6713352 {
    5.43 +    public static void main(String[] args) throws Exception {
    5.44 +        SwingUtilities.invokeAndWait(new Runnable() {
    5.45 +            public void run() {
    5.46 +                MyFileSystemView systemView = new MyFileSystemView();
    5.47 +
    5.48 +                synchronized (systemView) { // Get SystemView lock
    5.49 +                    new JFileChooser(systemView);
    5.50 +
    5.51 +                    // Wait a little bit. BasicDirectoryModel will lock Invoker and stop on
    5.52 +                    // the bug6713352.MyFileSystemView.getFiles() method
    5.53 +                    try {
    5.54 +                        Thread.sleep(5000);
    5.55 +                    } catch (InterruptedException e) {
    5.56 +                        throw new RuntimeException(e);
    5.57 +                    }
    5.58 +
    5.59 +                    try {
    5.60 +                        System.out.println("Try to get Invokers lock");
    5.61 +
    5.62 +                        ShellFolder.getShellFolder(new File("c:/")).listFiles(true);
    5.63 +                    } catch (FileNotFoundException e) {
    5.64 +                        throw new RuntimeException(e);
    5.65 +                    }
    5.66 +                }
    5.67 +
    5.68 +                // To avoid RejectedExecutionException in BasicDirectoryModel wait a second
    5.69 +                try {
    5.70 +                    Thread.sleep(1000);
    5.71 +                } catch (InterruptedException e) {
    5.72 +                    throw new RuntimeException(e);
    5.73 +                }
    5.74 +            }
    5.75 +        });
    5.76 +    }
    5.77 +
    5.78 +    private static class MyFileSystemView extends FileSystemView {
    5.79 +
    5.80 +        public File createNewFolder(File containingDir) throws IOException {
    5.81 +            return null;
    5.82 +        }
    5.83 +
    5.84 +        public File[] getFiles(File dir, boolean useFileHiding) {
    5.85 +            System.out.println("getFiles start");
    5.86 +
    5.87 +            File[] result;
    5.88 +
    5.89 +            synchronized (this) {
    5.90 +                result = super.getFiles(dir, useFileHiding);
    5.91 +            }
    5.92 +
    5.93 +            System.out.println("getFiles finished");
    5.94 +
    5.95 +            return result;
    5.96 +        }
    5.97 +
    5.98 +        public synchronized Boolean isTraversable(File f) {
    5.99 +            return super.isTraversable(f);
   5.100 +        }
   5.101 +    }
   5.102 +}