Skip to content

Conversation

@jonahgraham
Copy link
Contributor

This PR was originally #2198 but it seems to have disappeared (404) - the original PR had passed ECA

The fix in commit 215a618 for issue #2096 introduced an O(n^2) time complexity regression when projects have multiple source folders sharing the same output location (like SWT with 26 source roots).

The problem was that each source folder caused a new CompositeApiTypeContainer to be created, wrapping all previous containers plus a new one. This resulted in exponential duplication of container visits during API analysis.

The fix modifies ProjectTypeContainer to support multiple IPackageFragmentRoots, allowing a single container to discover packages from all source folders that share the same output location. This avoids creating composite containers and maintains O(n) complexity.

Fixes #2197

The fix in commit 215a618 for issue eclipse-pde#2096 introduced an O(n^2) time
complexity regression when projects have multiple source folders
sharing the same output location (like SWT with 26 source roots).

The problem was that each source folder caused a new CompositeApiTypeContainer
to be created, wrapping all previous containers plus a new one. This
resulted in exponential duplication of container visits during API analysis.

The fix modifies ProjectTypeContainer to support multiple
IPackageFragmentRoots, allowing a single container to discover packages
from all source folders that share the same output location. This avoids
creating composite containers and maintains O(n) complexity.

Fixes eclipse-pde#2197

Signed-off-by: moaead <moaead@users.noreply.github.com>
@jonahgraham
Copy link
Contributor Author

I checked out the code @moaead originally wrote before the PR disappeared and it looks good to me - the SWT build time has returned to normal (i.e. quite fast) with it.

The test that @laeubi originally wrote for 215a618 has been updated and it passes. However I am hoping that @laeubi can review this to make sure that the test itself still addresses the issue raised in #2096

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes an O(n²) performance regression introduced in a previous fix for issue #2096. The regression occurred when projects had multiple source folders sharing the same output location (e.g., SWT with 26 source roots), where each source folder caused creation of a new CompositeApiTypeContainer wrapping all previous containers, leading to exponential duplication.

Changes:

  • Modified ProjectTypeContainer to support multiple IPackageFragmentRoot objects using a list instead of a single field
  • Added addPackageFragmentRoot() method to accumulate package fragment roots for shared output locations
  • Replaced composite container creation logic in ProjectComponent with calls to addPackageFragmentRoot()

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
ProjectTypeContainer.java Converted from single IPackageFragmentRoot field to CopyOnWriteArrayList, added addPackageFragmentRoot method, updated getPackageNames to iterate all roots
ProjectComponent.java Replaced CompositeApiTypeContainer creation logic with calls to addPackageFragmentRoot on existing ProjectTypeContainer instances
ProjectTypeContainerTests.java Updated test expectations from COMPOSITE to FOLDER container type with explanatory comments
Comments suppressed due to low confidence (1)

apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ProjectTypeContainer.java:188

  • The lazy initialization pattern for fPackageNames lacks proper synchronization, which could lead to race conditions when combined with the cache invalidation in addPackageFragmentRoot. If getPackageNames is called concurrently with addPackageFragmentRoot, or if multiple threads call getPackageNames simultaneously, the cache could be computed multiple times or a stale cache could be used. Consider adding synchronized blocks similar to the pattern used in AbstractApiTypeContainer.getApiTypeContainers() (lines 180-186 of that class), or making fPackageNames volatile and using double-checked locking.
	public String[] getPackageNames() throws CoreException {
		if (fPackageNames == null) {
			SortedSet<String> names = new TreeSet<>();
			for (IPackageFragmentRoot root : fPackageFragmentRoots) {
				if (root.exists()) {
					collectPackageNames(names, root);
				}
			}
			fPackageNames = names.toArray(String[]::new);
		}
		return fPackageNames;
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (root != null && !fPackageFragmentRoots.contains(root)) {
fPackageFragmentRoots.add(root);
// Clear cached package names so they will be recomputed
fPackageNames = null;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting fPackageNames to null without synchronization could cause visibility issues across threads and race conditions with concurrent reads in getPackageNames(). Consider using synchronized block or making fPackageNames volatile to ensure thread-safe cache invalidation.

Copilot uses AI. Check for mistakes.
@laeubi
Copy link
Contributor

laeubi commented Jan 21, 2026

However I am hoping that @laeubi can review this to make sure that the test itself still addresses the issue raised in #2096

The bad thing with API tools is that it is quite hard to test things without real examples. So my suggestion would be to try it out and maybe re-iterate on this as we are quite early in the release there is enough time to fix things as they are discovered so better sooner than later...

@github-actions
Copy link

Test Results

  650 files  +  361    650 suites  +361   56m 38s ⏱️ + 33m 41s
3 732 tests ±    0  3 595 ✅  -     1  136 💤 ±  0  1 ❌ +1 
8 462 runs  +4 620  8 076 ✅ +4 452  385 💤 +167  1 ❌ +1 

For more details on these failures, see this check.

Results for commit 0967a1b. ± Comparison against base commit 324fb67.

super(parent, IApiElement.API_TYPE_CONTAINER, container.getName());
this.fRoot = container;
this.fPackageFragmentRoot = Objects.requireNonNull(packageFragmentRoot);
if (packageFragmentRoot != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null should not be allowed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have fixed this in #2203 - but FWIW @laeubi added the javadoc saying "may be null" so it looks like @moaead simply implemented the code as it was javadoc. The PR removes the may be null from the javadoc too.

@laeubi if you can review to make sure your original intentions are still intact that will be appreciated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code has undergone some refactoring so the javadoc was just wrong.

@jonahgraham
Copy link
Contributor Author

macOS test failed with:

Caused by: java.io.IOException: Server returned HTTP code: 400 for URL https://download.eclipse.org/eclipse/updates/4.38/R-4.38-202512010920/content.xml.xz

I raised https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/issues/7067 as I have seen this error in a few other builds in the last couple of days.

@laeubi
Copy link
Contributor

laeubi commented Jan 21, 2026

I raised https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/issues/7067 as I have seen this error in a few other builds in the last couple of days.

See https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/issues/6783 :-\

@iloveeclipse
Copy link
Member

Is it OK to merge now or not? Would be good to have it in M2 build.

@laeubi
Copy link
Contributor

laeubi commented Jan 21, 2026

I have added a comment regarding degraded null check, a project should always have at least one fragment and do not allow null... beside that if Jenkins succeed I think we are fine.

@iloveeclipse
Copy link
Member

I have added a comment regarding degraded

Did you pushed your change?

@jonahgraham
Copy link
Contributor Author

@iloveeclipse I'm ok to merge now, I can provide a update to this pr with null check later today or tomorrow.

@iloveeclipse iloveeclipse merged commit 19e78d8 into eclipse-pde:master Jan 21, 2026
38 of 40 checks passed
@laeubi
Copy link
Contributor

laeubi commented Jan 21, 2026

@iloveeclipse
Copy link
Member

OK, let have it in.

jonahgraham added a commit to jonahgraham/eclipse.pde that referenced this pull request Jan 21, 2026
…iners

In all current use cases of this internal class the value passed in
was non-null anyway, so there is no functional change with this commit,
it simply prevents future changes from trying to pass in a null here

An extra complication is that in 02fa168
the javadoc was updated to allow null IPackageFragmentRoot but the code
did not match that.

Fixes eclipse-pde#2202 (comment)
jonahgraham added a commit to jonahgraham/eclipse.pde that referenced this pull request Jan 22, 2026
…iners

In all current use cases of this internal class the value passed in
was non-null anyway, so there is no functional change with this commit,
it simply prevents future changes from trying to pass in a null here

An extra complication is that in 02fa168
the javadoc was updated to allow null IPackageFragmentRoot but the code
did not match that.

Fixes eclipse-pde#2202 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API tools slow down when building SWT for the last few months (regression)

3 participants