JDK-8325179 : Race in BasicDirectoryModel.validateFileCache
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 8u301,11.0.10,15,17,21,22,23
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2024-02-02
  • Updated: 2024-06-26
  • Resolved: 2024-03-21
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 17 JDK 21 JDK 22 JDK 23 JDK 8
11.0.25-oracleFixed 17.0.13-oracleFixed 21.0.5-oracleFixed 22.0.2Fixed 23 b16Fixed 8u431Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
BasicDirectoryModel.validateFileCache starts a background thread, a FilesLoader object, to update the list of files in the current directory.

First, the method checks if there's an existing filesLoader and interrupts it.

Then it creates a new filesLoader.

The above is done without any synchronisation, which means if the method is called concurrently by several threads, several fileLoader threads could be started concurrently. If it happens, only the latest one may be interrupted.

A similar problem exists in invalidateFileCache, access to filesLoader field isn't protected by a lock.


BasicDirectoryModel.FilesLoader.cancelRunnables has a data race too. The 'runnable' field is a volatile field, but it's not enough.

It is possible that runnable is not null when the condition is checked but becomes null in the body of the if statement. It could be set a new value too.
Comments
[jdk11u-fix-request] Approval Request from Martin Should get backported for parity with 11.0.25-oracle. Applies cleanly and tier 1-4 have passed.
14-06-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk11u-dev/pull/2768 Date: 2024-06-13 12:53:53 +0000
13-06-2024

[jdk17u-fix-request] Approval Request from Martin Should get backported for parity with 17.0.13-oracle. Applies cleanly and tier 1-4 have passed.
11-06-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk17u-dev/pull/2558 Date: 2024-06-10 09:25:57 +0000
10-06-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk17u-dev/pull/2539 Date: 2024-06-05 14:06:32 +0000
05-06-2024

[jdk21u-fix-request] Approval Request from Martin Should get backported for parity with 21.0.5-oracle. Applies cleanly and tier 1-4 have passed.
01-06-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk21u-dev/pull/633 Date: 2024-05-31 15:43:11 +0000
31-05-2024

Regression test is provided in JDK-8331142.
24-05-2024

There was another variation of this bug in 8u: Exception in Thread-2: class java.lang.IllegalThreadStateException java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:710) at javax.swing.plaf.basic.BasicDirectoryModel.validateFileCache(BasicDirectoryModel.java:148) at sun.swing.FilePane.rescanCurrentDirectory(FilePane.java:1799) at javax.swing.plaf.metal.MetalFileChooserUI.rescanCurrentDirectory(MetalFileChooserUI.java:797) at javax.swing.JFileChooser.rescanCurrentDirectory(JFileChooser.java:625) at ConcurrentModification$Scanner.run(ConcurrentModification.java:204) at java.lang.Thread.run(Thread.java:750) Our analysis concluded that the thread which created the new thread was pre-empted after it had created the thread object but before the start() was called; in this scenario, another thread could interrupt the thread. Thus, when the start method is called, the state of the thread was not 0 and therefore the thread could not be started.
24-05-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk22u/pull/164 Date: 2024-04-24 15:12:52 +0000
24-04-2024

Fix request: 22u This bug fix resolves a race condition BasicDirectoryModel, it ensures there's only one Files Loading Thread. Clean backport (if applied on top of JDK-8323670). Medium risk.
17-04-2024

Providing a regression test for this bug is hard, the thread is created by BasicDirectoryModel. The only way I can think of is taking a snapshot of live threads, if there are several instances of BasicDirectoryModel.FilesLoader ("Basic L&F File Loading Thread") in any of the snapshots, the test fails.
22-03-2024

Changeset: e66788c1 Author: Alexey Ivanov <aivanov@openjdk.org> Date: 2024-03-21 16:03:30 +0000 URL: https://git.openjdk.org/jdk/commit/e66788c16563d343f6cccd2807a251ccc6f9b64a
21-03-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/18111 Date: 2024-03-04 20:21:30 +0000
04-03-2024

The latest version of the test: https://github.com/aivanov-jdk/jdk/blob/c7f53dedc334c0b12b389c73fdbcc9243e761bc4/test/jdk/javax/swing/JFileChooser/FileSystemView/BasicDirectoryModelConcurrency.java It's in the ‘8323670-BasicDirectoryModel-concurrency’ branch: https://github.com/aivanov-jdk/jdk/blob/8323670-BasicDirectoryModel-concurrency/test/jdk/javax/swing/JFileChooser/FileSystemView/BasicDirectoryModelConcurrency.java The latest version of the debug traces: https://github.com/aivanov-jdk/jdk/blob/09163b638a0e759f260637f3f17c542b5359b65e/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDirectoryModel.java Both the traces and the test are in the ‘8323670-BasicDirectoryModel-concurrency-debug’ branch. The test is not stable enough to contribute it to OpenJDK. https://bugs.openjdk.org/browse/JDK-8323670?focusedId=14644835&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14644835 https://github.com/openjdk/jdk/pull/17462#issuecomment-1914844026
02-02-2024

I found this issue while I was running a test for JDK-8323670. If I call fileChooser.rescanCurrentDirectory from 5 threads concurrently, the following output is possible: > validateFileCache Thread-0 - null < validateFileCache Thread-0 - FilesLoader(1, File Loading Thread 1) > validateFileCache Thread-0 - FilesLoader(1, File Loading Thread 1) interrupt File Loading Thread 1 < validateFileCache Thread-0 - FilesLoader(2, File Loading Thread 2) > validateFileCache Thread-2 - FilesLoader(2, File Loading Thread 2) interrupt File Loading Thread 2 > validateFileCache Thread-6 - FilesLoader(2, File Loading Thread 2) > validateFileCache Thread-4 - FilesLoader(2, File Loading Thread 2) interrupt File Loading Thread 2 interrupt File Loading Thread 2 > validateFileCache Thread-3 - FilesLoader(2, File Loading Thread 2) interrupt File Loading Thread 2 > validateFileCache Thread-5 - FilesLoader(2, File Loading Thread 2) interrupt File Loading Thread 2 < validateFileCache Thread-2 - FilesLoader(3, File Loading Thread 3) interrupt File Loading Thread 2 < validateFileCache Thread-4 - FilesLoader(4, File Loading Thread 4) < validateFileCache Thread-6 - FilesLoader(5, File Loading Thread 5) < validateFileCache Thread-3 - FilesLoader(6, File Loading Thread 6) < validateFileCache Thread-5 - FilesLoader(7, File Loading Thread 7) At first Thread-0 enters BasicDirectoryModel.validateFileCache and starts FilesLoader(1). It enters validateFileCache again, in this case it interrupts FilesLoader(1) and starts a new one, FilesLoader(2). (Thread-0 is the runner thread which created JFileChooser.) At this point, several threads — Thread-2, Thread-6, Thread-4, Thread-3, Thread-5 — enter validateFileCache. All these threads see FilesLoader(2) as the active file loader, they interrupt it and each creates a new instance FilesLoader. Thus, instead of one filesLoader, there are five. Each thread produces its own instance of DoChangeContents that's assigned to 'runnable', all five are submitted to EDT. Only the latest one, with fetchID=7, fires an event; others exit because their fid and the current fetchID do not match.
02-02-2024