This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

Bug 268028

Summary: Performance issue in (jpda) MainProjectManager.getMainProject() -> isDependent(lastSelectedProject, current)
Product: ide Reporter: NukemBy
Component: PerformanceAssignee: Tomas Hurka <thurka>
Status: NEW ---    
Severity: normal    
Priority: P3    
Version: Dev   
Hardware: PC   
OS: Windows 7   
Issue Type: DEFECT Exception Reporter:
Attachments: self-profiler for getMainProject

Description NukemBy 2016-09-14 17:05:16 UTC
Created attachment 162050 [details]
self-profiler for getMainProject

Method MainProjectManager.getMainProject() is called rather frequently in when doing Java development in NB. It is invoked per almost each user action to update UI (enabling/disabling of actions) and is invoked 4 times to start a unit test (2 * 2 threads, each invocation is around 120-150ms), see attached screenshot.

Origin of this issue is (https://netbeans.org/bugzilla/show_bug.cgi?id=267969 - Running single simple unit test in debug takes 6+ seconds) and it may happen that it will be resolved by fixing of that issue (https://netbeans.org/bugzilla/show_bug.cgi?id=268027 - FileUtil.normalizedRef cache is cleaned too frequently).

So ... what is going on inside:

        public Project getMainProject () {
            Project lastSelectedProject = lastSelectedProjectRef.get();
            Project current = currentProject.get();
            boolean isMain = isMainProject;
            if (isMain && lastSelectedProject != null &&
                    lastSelectedProject != current  &&
  --->              !isDependent(lastSelectedProject, current )) {  <---
                // If there's a main project set, but the current project has no
                // dependency on it, return the current project.
                return lastSelectedProject;
            } else {
                return current;
            }
        }

implementation of isDependent() collects source roots for both projects and checks that way if one project depends on another

    private static boolean isDependent(Project p1, Project p2) {
        Set<URL> p1Roots = getProjectRoots(p1);
        Set<URL> p2Roots = getProjectRoots(p2);

        for (URL root : p2Roots) {
            Set<URL> dependentRoots = SourceUtils.getDependentRoots(root);
            for (URL sr : p1Roots) {
                if (dependentRoots.contains(sr)) {
                    return true;
                }
            }
        }
        return false;
    }

The problem here is that 
- collection of source roots is rather CPU consuming operation (because of FileUtil.normalizeFile())
- 'isDependent' status actually changes infrequently - when configuration of some project change (and that may not happen during the whole day).

I think "the most correct" solution would be listening to changes in configurations of projects - and updating 'isDependent' status upon real changes. If that is technically hard to implement - caching of 'isDependent' status for few seconds would be sufficient (and refreshing it in 'only one background thread' - currently calculation of 'isDependent' is performed in parallel in each background tread which may need the 'getMainProject()').

For reference - one of the call stacks with MainProjectManager.getMainProject()
-------------------------------------------------------------------------------
"Refresh Editor Context"
    at org.openide.filesystems.FileUtil.normalizeFileCached(FileUtil.java:1638)
    at org.openide.filesystems.FileUtil.normalizeFile(FileUtil.java:1629)
    at org.netbeans.modules.masterfs.filebasedfs.FileBasedURLMapper.getFileObjects(FileBasedURLMapper.java:98)
    at org.netbeans.modules.masterfs.MasterURLMapper.getFileObjects(MasterURLMapper.java:65)
    at org.openide.filesystems.URLMapper.findFileObject(URLMapper.java:213)
    at org.netbeans.api.java.source.SourceUtils.getDependentRoots(SourceUtils.java:790)
    at org.netbeans.api.java.source.SourceUtils.getDependentRoots(SourceUtils.java:770)
    at org.netbeans.modules.debugger.jpda.projectsui.MainProjectManager.isDependent(MainProjectManager.java:173)
--> at org.netbeans.modules.debugger.jpda.projectsui.MainProjectManager.getMainProject(MainProjectManager.java:121)
    at org.netbeans.modules.debugger.jpda.projectsui.RunToCursorActionProvider.shouldBeEnabled(RunToCursorActionProvider.java:210)
    at org.netbeans.modules.debugger.jpda.projectsui.RunToCursorActionProvider.access$300(RunToCursorActionProvider.java:81)
Comment 1 NukemBy 2016-09-15 16:20:48 UTC
Root cause for this bug is here: https://netbeans.org/bugzilla/show_bug.cgi?id=268045