Repository navigation
Conversation
…ojects with TestProjectOpenedHookImpl
…st up-to-date array of projects
|
|
||
| public boolean waitFinished(long toMillis); | ||
|
|
||
| public void enter(); |
There was a problem hiding this comment.
- These
enterandexitmethods do not fit into the intended workflow. - they should be removed and replaced by ...
- a long running operations in a dedicated request processor thread
eppleton
left a comment
There was a problem hiding this comment.
Thanks for working on this. I reviewed 0674e5c and checked the points inline locally against master. The main ones are the openProjects() future contract, startup events firing under OpenProjectList.MUTEX, a deadlock in replaceProjects, and a group selected before load opening the old projects. A few test-helper issues in OpenProjectListTest hide failures.
| @@ -232,7 +210,37 @@ static void preferredProject(final Project lazyP) { | |||
| } | |||
|
|
|||
| public Future<Project[]> openProjectsAPI() { | |||
There was a problem hiding this comment.
This Future no longer waits for an open()/close() that is in progress. On master get() also waited until entered == 0, which is what the OpenProjects.openProjects() javadoc promises ("awaits for current modifications to finish"). isDone() still checks entered, so it can return false while get() returns immediately, and enteredZeroed is now signalled but never awaited.
With a project whose open hook blocks, master's get(300ms) throws TimeoutException; this branch returns the list. OpenProjectListTest.testProjectOpenedClosed and testProjectClosedRace fail on every isolated run here and pass on master. CI won't show it, because the class is @RandomlyFails and CI sets ignore.random.failures=true.
I see the plan to remove enter/exit. Whatever replaces them needs to keep this contract, and dropping @RandomlyFails from OpenProjectListTest would let CI guard it.
| mainProject = unwrapProject(mainProject); | ||
| getRecentTemplates().addAll(recentTemplates); | ||
|
|
||
| MUTEX.postReadRequest(() -> { |
There was a problem hiding this comment.
updateGlobalState is called inside writeAccess, so postReadRequest is delayed until the write lock is released and then runs in read access. As a result, PROPERTY_OPEN_PROJECTS / PROPERTY_MAIN_PROJECT at startup are delivered with OpenProjectList.MUTEX held; on master they fired with no lock. A listener on this branch sees MUTEX.isReadAccess() == true; on master it is false.
All OpenProjects listeners in the IDE get these events via OpenProjectsTrampolineImpl, so they now run under the lock the class comment says must never be held while reaching ProjectManager.MUTEX. Any listener needing write access (e.g. setMainProject) hits the read→write upgrade error. Could the callback be split into "apply state" (under the lock) and "fire" (after writeAccess returns), as before?
|
|
||
| private final void replaceProjectsImpl(List<LazyProject> projects, URL mainProject) { | ||
| if (openProjects != null) { | ||
| close(openProjects.toArray(Project[]::new), false); |
There was a problem hiding this comment.
This deadlocks when replaceProjects is called while the startup load is still running. replaceProjects holds MUTEX.writeAccess and calls close(). LOAD.closeBeforeOpen returns false for a project the loader has already dequeued, so close() calls LOAD.waitFinished(0). The loader then blocks on MUTEX (notifyOpened → prepareTemplates → getDefault() → readAccess). I reproduced it locally: both threads hang permanently.
Independent of the deadlock, this runs close() (project saves, property changes, closeAllDocuments) under MUTEX. The class comment warns against reaching ProjectManager.MUTEX from there.
| } | ||
| }; | ||
| pchSupport = new PropertyChangeSupport( this ); | ||
| replaceProjectsImpl(loadProjectList(), main); |
There was a problem hiding this comment.
Capturing the URLs here, in the constructor, changes behaviour when a group is selected before projects are loaded (e.g. --open-group at startup). Group.setActiveGroup with projectsLoaded == false first calls OpenProjects.getDefault().getOpenProjects(), which creates OpenProjectList and captures the old URLs. Only afterwards does it write the new group's URLs with setOpenProjectsURLsAsStrings. On master loadInBackground read the URLs when it ran, so it picked up the new group.
With saved settings [a] and a group {b} activated before load, master opens b and this branch opens a.
| Iterator<ExtIcon> iconIt = icons.iterator(); | ||
|
|
||
| while(urlIt.hasNext() && namesIt.hasNext() && iconIt.hasNext()) { |
There was a problem hiding this comment.
The projects opened at startup now come from this zip. A URL without a matching name and icon is silently skipped, whereas on master the URL list alone decided what was opened. Group.java:187 writes only the URL list, so the three lists can get out of step.
|
|
||
| void removeOpenFile(FileObject fo) { | ||
| var previousValue = openFiles.remove(fo.toURL().toExternalForm()); | ||
| assertNotNull("There should be previous value for " + fo); |
There was a problem hiding this comment.
assertNotNull(String) only checks the message string, so this always passes; previousValue is never used.
| public Set<String> openFiles = new HashSet<String>(); | ||
| public Map<Project,Set<String>> urls4project = new HashMap<Project,Set<String>>(); | ||
| public static final class TestOpenCloseProjectDocument implements ProjectUtilities.OpenCloseProjectDocument { | ||
| private final Map<String, Exception> openFiles = new HashMap<>(); |
There was a problem hiding this comment.
openFiles is written from OPENING_RP (via ProjectUtilities.openProjectFiles) and read from the test thread. Maybe use a synchronized or concurrent map?
| } | ||
| assertFalse ("Document f1_1_open isn't loaded.", handler.openFiles.contains (f1_1_open.toURL ().toExternalForm ())); | ||
| assertFalse ("Document f1_2_open isn't loaded.", handler.openFiles.contains (f1_2_open.toURL ().toExternalForm ())); | ||
| handler.assertNotOpened("Document f1_1_open is loaded.",f1_1_open); |
There was a problem hiding this comment.
Nit: "is loaded." on an assertNotOpened. The same applies to the next line.
| }); | ||
| } | ||
|
|
||
| @NbBundle.Messages(value = {"#NOI18N", "LOAD_PROJECTS_ON_START=true"}) |
There was a problem hiding this comment.
Nit: LOAD_PROJECTS_ON_START is now used only in OpenProjectList.loadProjectList, so the @Messages declaration could move there.
| * <p> | ||
| * <h3>How it Should Work?</h3> | ||
| * | ||
| * When there is a needed to change the list of opened projects, then let's |
There was a problem hiding this comment.
Nit, javadoc typos: "When there is a needed to change" here, "currated" (line 333), and a stray } at the end of the finishOpening @return (line 345).
|
Hopefully this work might address an occasionally seen issue where the loading gets "stuck" completely. More often in my platform IDE than NetBeans, although it does show there. See my comment on a recently reported issue at #9657 (comment) The project gets stuck as |
OpenProjectList@RandolyFailingfrom the tests in there