Skip to content

Delete ShadowTreeRevision on background thread #50997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

rozele
Copy link
Contributor

@rozele rozele commented Apr 29, 2025

Summary:
In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the baseRevision_ instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

To minimize thread initialization costs, this change adds an AsyncDestructor helper that uses a single background thread and a queue of to-be-destroyed objects that are released in a run loop.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

Changelog

[Internal]

Differential Revision: D73688009

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 29, 2025
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 29, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

To minimize thread initialization costs, this change adds an AsyncDestructor helper that uses a single background thread and a queue of to-be-destroyed objects that are released in a run loop.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from c793405 to 4770bcd Compare April 29, 2025 17:53
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

@rozele rozele force-pushed the export-D73688009 branch from 4770bcd to 4352c62 Compare April 29, 2025 18:04
rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 29, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

To minimize thread initialization costs, this change adds an AsyncDestructor helper that uses a single background thread and a queue of to-be-destroyed objects that are released in a run loop.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 29, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

To minimize thread initialization costs, this change adds an AsyncDestructor helper that uses a single background thread and a queue of to-be-destroyed objects that are released in a run loop.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch 2 times, most recently from 516dda4 to 76035b5 Compare April 29, 2025 19:29
rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 29, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

To minimize thread initialization costs, this change adds an AsyncDestructor helper that uses a single background thread and a queue of to-be-destroyed objects that are released in a run loop.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 29, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

To minimize thread initialization costs, this change adds an AsyncDestructor helper that uses a single background thread and a queue of to-be-destroyed objects that are released in a run loop.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from 76035b5 to 3620cfd Compare April 29, 2025 19:33
rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 29, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

To minimize thread initialization costs, this change adds an AsyncDestructor helper that uses a single background thread and a queue of to-be-destroyed objects that are released in a run loop.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from 3620cfd to df7cbd9 Compare April 29, 2025 19:40
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

@rozele rozele force-pushed the export-D73688009 branch from df7cbd9 to fa6b8c4 Compare April 30, 2025 18:16
rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 30, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Following how most other threading in react-native works, this change also introduces a thread pool abstraction that should be supplied by host platforms (LowPriorityThreadPool). The implementation of this LowPriorityThreadPool will be introduced in future diffs for each platform.

The AsyncDestructor abstraction simply moves the object into a captured variable and dispatches it to the LowPriorityThreadPool.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 30, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor will be introduced in future diffs for each platform.

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor. 

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from fa6b8c4 to 2160b50 Compare April 30, 2025 19:10
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request Apr 30, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor will be introduced in future diffs for each platform.

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor. 

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from 2160b50 to 894c0b9 Compare April 30, 2025 19:14
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

@rozele rozele force-pushed the export-D73688009 branch from 33f9944 to dd31bdc Compare May 2, 2025 01:49
rozele added a commit to rozele/react-native-macos that referenced this pull request May 2, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

@rozele rozele force-pushed the export-D73688009 branch from dd31bdc to 633cacc Compare May 2, 2025 11:24
rozele added a commit to rozele/react-native-macos that referenced this pull request May 2, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

@rozele rozele force-pushed the export-D73688009 branch from 633cacc to 8ea8c03 Compare May 2, 2025 11:32
rozele added a commit to rozele/react-native-macos that referenced this pull request May 2, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request May 2, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from 8ea8c03 to a1cad99 Compare May 2, 2025 13:31
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request May 2, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from a1cad99 to e7170ac Compare May 2, 2025 13:37
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

rozele added a commit to rozele/react-native-macos that referenced this pull request May 2, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch 2 times, most recently from 35e0782 to 3ffada8 Compare May 5, 2025 05:30
rozele added a commit to rozele/react-native-macos that referenced this pull request May 5, 2025
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor. 

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

@rozele rozele force-pushed the export-D73688009 branch from 3ffada8 to 487e091 Compare May 5, 2025 05:34
rozele added a commit to rozele/react-native-macos that referenced this pull request May 5, 2025
Summary:
Pull Request resolved: facebook#50997

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
Summary:

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor. 

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009
@rozele rozele force-pushed the export-D73688009 branch from 487e091 to a2f67d3 Compare May 5, 2025 06:16
@facebook-github-bot
Copy link
Contributor

This pull request was exported from Phabricator. Differential Revision: D73688009

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @rozele in d9823d8

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label May 5, 2025
@facebook-github-bot
Copy link
Contributor

This pull request has been merged in d9823d8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. fb-exported Merged This PR has been merged. p: Facebook Partner: Facebook Partner
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants