Skip to content

Fix ProfileScorer.getChildren() to expose wrapped scorer in scorer tree#20607

Merged
owaiskazi19 merged 6 commits intoopensearch-project:mainfrom
martin-gaievski:fix-profiler-scorer-getchildren
Feb 18, 2026
Merged

Fix ProfileScorer.getChildren() to expose wrapped scorer in scorer tree#20607
owaiskazi19 merged 6 commits intoopensearch-project:mainfrom
martin-gaievski:fix-profiler-scorer-getchildren

Conversation

@martin-gaievski
Copy link
Copy Markdown
Member

@martin-gaievski martin-gaievski commented Feb 12, 2026

Description

ProfileScorer.getChildren() currently delegates directly to the wrapped scorer's getChildren(),
which skips the ProfileScorer level in the scorer tree. This makes the wrapped scorer invisible to
any code traversing the scorer hierarchy via the standard Lucene Scorable.getChildren() API.

This change makes ProfileScorer expose the wrapped scorer as a child with the "PROFILED"
relationship label, allowing plugins and custom collectors to discover wrapped scorers through
profiling wrappers using standard tree traversal — without requiring reflection.

Changes

  • ProfileScorer.java: Changed getChildren() from return scorer.getChildren() to
    return Collections.singletonList(new ChildScorable(scorer, "PROFILED"))
  • ProfileScorerTests.java: Added testGetChildren_exposesWrappedScorerAsChild() to verify
    the new behavior

Related Issues

Resolves #20548

PR #20549 added getWrappedScorer() — a public method on ProfileScorer that returns the wrapped scorer. This was intended to let neural-search unwrap ProfileScorer to find HybridQueryScorer.

When implementing the neural-search side, we discovered that getWrappedScorer() alone doesn't solve the problem because ProfileScorer is a package-private final class. Even though getWrappedScorer() is a public method, Java's access control system blocks Method.invoke() from external modules (plugins) on methods of non-public classes. The plugin code gets:

IllegalAccessException: class LeafCollector cannot access a member of class 
org.opensearch.search.profile.query.ProfileScorer with modifiers "public"

This means the neural-search plugin had to use method.setAccessible(true) + @SuppressForbidden to call getWrappedScorer() via reflection — defeating the purpose of having a clean public API.

Check List

  • Functionality includes testing.
  • API changes companion pull request created, if applicable.
  • Public documentation issue/PR created, if applicable.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 12, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR modifies ProfileScorer to expose the wrapped scorer as a child through the getChildren() method, which now returns a singleton list containing a ChildScorable with "PROFILED" relationship instead of delegating to the wrapped scorer's children. Includes corresponding test coverage and changelog documentation.

Changes

Cohort / File(s) Summary
ProfileScorer Implementation
server/src/main/java/org/opensearch/search/profile/query/ProfileScorer.java
Added java.util.Collections import. Modified getChildren() to return a singleton list wrapping the scorer in a ChildScorable with "PROFILED" relationship, exposing the wrapped scorer for plugin access instead of delegating to the wrapped scorer's children.
Test Coverage
server/src/test/java/org/opensearch/search/profile/query/ProfileScorerTests.java
Added testGetChildren_exposesWrappedScorerAsChild() test method verifying that getChildren() returns exactly one child (the wrapped scorer) with PROFILED relationship. Updated imports to include java.util.Collection.
Documentation
CHANGELOG.md
Added Fixed entry under Unreleased 3.x describing that ProfileScorer.getChildren() now exposes the wrapped scorer for plugin compatibility.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • opensearch-project/OpenSearch#20549: Implements an alternative approach (getWrappedScorer() accessor method) to expose the wrapped scorer in ProfileScorer for the same plugin compatibility use case.
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: fixing ProfileScorer.getChildren() to expose the wrapped scorer in the scorer tree.
Linked Issues check ✅ Passed The PR implementation aligns with issue #20548's objective of exposing the wrapped scorer, though via getChildren() rather than a dedicated getWrappedScorer() method.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: ProfileScorer.getChildren() modification, import addition, and corresponding test coverage.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering problem statement, solution, changes, related issues, and checklist items as required by the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for 6fddaa4: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@martin-gaievski
Copy link
Copy Markdown
Member Author

Looks like rarely failing test, not related to this change, passes in my local environment

Task :server:test

REPRODUCE WITH: ./gradlew ':server:test' --tests 'org.opensearch.index.shard.IndexShardTests.testRestoreSearchOnlyShardFromStoreOnNewNode' -Dtests.seed=B3BDACD5418087E4 -Dtests.security.manager=true -Dtests.jvm.argline="-XX:TieredStopAtLevel=1 -XX:ReservedCodeCacheSize=64m" -Dtests.locale=ha-NE -Dtests.timezone=Asia/Makassar -Druntime.java=25

IndexShardTests > testRestoreSearchOnlyShardFromStoreOnNewNode FAILED
    [index][[index][0]] IndexShardRecoveryException[Failed to recover from gateway]; nested: CorruptIndexException[misplaced codec footer (file truncated?): length=0 but footerLength==16 (resource=metadata__9223372036854775805__9223372036854775804__9223372036854775802__9223372036854775805__-1039764442__9223370265996427162__2)];
        at __randomizedtesting.SeedInfo.seed([B3BDACD5418087E4:7369770EE6AE81DC]:0)
        at app//org.opensearch.index.shard.StoreRecovery.internalRecoverFromStoreForSearchReplica(StoreRecovery.java:815)
        at app//org.opensearch.index.shard.StoreRecovery.lambda$recoverFromStore$0(StoreRecovery.java:127)
        at app//org.opensearch.core.action.ActionListener.completeWith(ActionListener.java:344)
        at app//org.opensearch.index.shard.StoreRecovery.recoverFromStore(StoreRecovery.java:124)
        at app//org.opensearch.index.shard.IndexShard.recoverFromStore(IndexShard.java:3198)
        at app//org.opensearch.index.shard.IndexShardTestCase.recoverFromStore(IndexShardTestCase.java:1479)
        at app//org.opensearch.index.shard.IndexShardTestCase.recoverShardFromStore(IndexShardTestCase.java:1085)
        at app//org.opensearch.index.shard.IndexShardTests.testRestoreSearchOnlyShardFromStoreOnNewNode(IndexShardTests.java:3092)

        Caused by:
        org.apache.lucene.index.CorruptIndexException: misplaced codec footer (file truncated?): length=0 but footerLength==16 (resource=metadata__9223372036854775805__9223372036854775804__9223372036854775802__9223372036854775805__-1039764442__9223370265996427162__2)
            at app//org.apache.lucene.codecs.CodecUtil.checksumEntireFile(CodecUtil.java:616)
            at app//org.opensearch.common.io.VersionedCodecStreamWrapper.readStream(VersionedCodecStreamWrapper.java:63)
            at app//org.opensearch.index.store.RemoteSegmentStoreDirectory.readMetadataFile(RemoteSegmentStoreDirectory.java:282)
            at app//org.opensearch.index.store.RemoteSegmentStoreDirectory.readLatestMetadataFile(RemoteSegmentStoreDirectory.java:271)
            at app//org.opensearch.index.store.RemoteSegmentStoreDirectory.init(RemoteSegmentStoreDirectory.java:178)
            at app//org.opensearch.index.shard.IndexShard.syncSegmentsFromRemoteSegmentStore(IndexShard.java:5497)
            at app//org.opensearch.index.shard.IndexShard.syncSegmentsFromRemoteSegmentStore(IndexShard.java:5480)
            at app//org.opensearch.index.shard.IndexShard.innerOpenEngineAndTranslog(IndexShard.java:2871)
            at app//org.opensearch.index.shard.IndexShard.openEngineAndSkipTranslogRecovery(IndexShard.java:2838)
            at app//org.opensearch.index.shard.IndexShard.openEngineAndSkipTranslogRecoveryFromSnapshot(IndexShard.java:2832)
            at app//org.opensearch.index.shard.StoreRecovery.completeRecovery(StoreRecovery.java:843)
            at app//org.opensearch.index.shard.StoreRecovery.internalRecoverFromStoreForSearchReplica(StoreRecovery.java:813)
            ... 7 more

> Task :plugins:mapper-murmur3:bundlePlugin

@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for d9166f3: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@martin-gaievski martin-gaievski force-pushed the fix-profiler-scorer-getchildren branch from 5b8fbf1 to 39bf65d Compare February 16, 2026 21:56
Copy link
Copy Markdown
Member

@owaiskazi19 owaiskazi19 left a comment

Choose a reason for hiding this comment

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

Looks good. Thanks @martin-gaievski . Will wait for @jainankitk to take a look before merging in

@github-actions
Copy link
Copy Markdown
Contributor

❕ Gradle check result for 39bf65d: UNSTABLE

Please review all flaky tests that succeeded after retry and create an issue if one does not already exist to track the flaky failure.

Signed-off-by: Martin Gaievski <gaievski@amazon.com>
Signed-off-by: Martin Gaievski <gaievski@amazon.com>
Signed-off-by: Martin Gaievski <gaievski@amazon.com>
Signed-off-by: Martin Gaievski <gaievski@amazon.com>
@martin-gaievski martin-gaievski force-pushed the fix-profiler-scorer-getchildren branch from 39bf65d to 8245b9f Compare February 17, 2026 17:12
@github-actions
Copy link
Copy Markdown
Contributor

❌ Gradle check result for 8245b9f: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Signed-off-by: Martin Gaievski <gaievski@amazon.com>
@martin-gaievski martin-gaievski force-pushed the fix-profiler-scorer-getchildren branch from 8245b9f to bc1d39e Compare February 17, 2026 17:29
@github-actions
Copy link
Copy Markdown
Contributor

✅ Gradle check result for bc1d39e: SUCCESS

Copy link
Copy Markdown
Contributor

@jainankitk jainankitk left a comment

Choose a reason for hiding this comment

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

Thanks @martin-gaievski for addressing the review comment. Can we implement this interface for ProfilingAggregator, ProfileCollector and any other class providing access to the delegate?

…tCollector

Signed-off-by: Martin Gaievski <gaievski@amazon.com>
@martin-gaievski
Copy link
Copy Markdown
Member Author

Thanks @martin-gaievski for addressing the review comment. Can we implement this interface for ProfilingAggregator, ProfileCollector and any other class providing access to the delegate?

We can add the interface to ProfilingAggregator and ProfilingLeafBucketCollector, just pushed new commit with corresponding change

@github-actions
Copy link
Copy Markdown
Contributor

✅ Gradle check result for c8ee7ef: SUCCESS

@owaiskazi19 owaiskazi19 merged commit 14749f7 into opensearch-project:main Feb 18, 2026
35 checks passed
tanyabti pushed a commit to tanyabti/OpenSearch that referenced this pull request Feb 24, 2026
…ee (opensearch-project#20607)

* Fix ProfileScorer.getChildren() to expose wrapped scorer in scorer tree

Signed-off-by: Martin Gaievski <gaievski@amazon.com>

* Changed approach to use new ScoreWrapper interface

Signed-off-by: Martin Gaievski <gaievski@amazon.com>

* Renamed interface to WrappedScorerAccessor, added unit test

Signed-off-by: Martin Gaievski <gaievski@amazon.com>

* Improving tests, corrected changelog

Signed-off-by: Martin Gaievski <gaievski@amazon.com>

* Changed to generic interface ProfilingWrapper

Signed-off-by: Martin Gaievski <gaievski@amazon.com>

* Adding ProfilingWrapper to ProfilingAggregator and ProfilingLeafBucketCollector

Signed-off-by: Martin Gaievski <gaievski@amazon.com>

---------

Signed-off-by: Martin Gaievski <gaievski@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Enhancement or improvement to existing feature or request lucene Search:Query Capabilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Add getWrappedScorer() method to ProfileScorer for consistency with ProfileCollector

3 participants