Skip to content

iOS: test GPU availability and task queue behaviour - #190836

Merged
cbracken merged 1 commit into
flutter:masterfrom
cbracken:test-task-queue-behaviour
Aug 11, 2026
Merged

cbracken merged 1 commit into
flutter:masterfrom
cbracken:test-task-queue-behaviour

Conversation

@cbracken

@cbracken cbracken commented Aug 10, 2026 •

Copy link
Copy Markdown
Member

Adds tests for two behaviours the embedder API migration has to preserve, both at the layer a plugin or the framework actually sees rather than the layer the current implementation happens to use:

First is GPU availability:

Currently, createShell: re-derives isGpuDisabled from the live view controller or application state immediately before creating the shell, and clobbers whatever the property was set to beforehand. This is an easy thing to get wrong later: the "obvious" handling via FlutterEngineRun would be to take the initial GPU availability as a project argument, but passing the property straight through would resurrect exactly the stale value this code goes out of its way to drop.

Second is Task queues:

PlatformMessageHandlerIosTest covers PlatformMessageHandlerIos behaviour on its own, but nothing covered the path a plugin takes to reach it, through

  • -[FlutterEngine makeBackgroundTaskQueue]
  • -[FlutterEngine setMessageHandlerOnChannel:binaryMessageHandler:taskQueue:]

EmbedderPlatformMessageHandler always trampolines to the platform thread, so both halves of that contract will have to be rebuilt explicitly for embedder API migration.

The GPU test locks in the current behaviour. I'll address the TODO in the next patch to clean up the API. But this is a nice way to document/verify the behavioural diff in the tests.

Issue: #112232
Issue: #190835

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@cbracken
cbracken requested a review from a team as a code owner August 10, 2026 12:46
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Aug 10, 2026
@github-actions github-actions Bot added platform-ios iOS applications specifically engine flutter/engine related. See also e: labels. team-ios Owned by iOS platform team labels Aug 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request adds unit tests to FlutterEngineTest.mm to verify GPU state behavior during shell creation and to ensure that platform messages are delivered on the correct threads depending on the task queue configuration. It also introduces a helper class TestPlatformMessageResponse to track message completion. Feedback suggests making the constructor of TestPlatformMessageResponse private to prevent accidental stack allocation of a ref-counted object, and omitting the unused parameter in Complete to avoid compiler warnings.

Comment on lines +35 to +42
class TestPlatformMessageResponse : public flutter::PlatformMessageResponse {
public:
static fml::RefPtr<TestPlatformMessageResponse> Create() {
return fml::AdoptRef(new TestPlatformMessageResponse());
}
void Complete(std::unique_ptr<fml::Mapping> data) override { is_complete_ = true; }
void CompleteEmpty() override { is_complete_ = true; }
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To prevent accidental stack allocation or direct instantiation of TestPlatformMessageResponse (which is ref-counted and should only be managed via fml::RefPtr), the constructor should be made private. Additionally, the unused parameter data in Complete can be omitted to avoid potential compiler warnings about unused parameters.

class TestPlatformMessageResponse : public flutter::PlatformMessageResponse {
 public:
  static fml::RefPtr<TestPlatformMessageResponse> Create() {
    return fml::AdoptRef(new TestPlatformMessageResponse());
  }
  void Complete(std::unique_ptr<fml::Mapping>) override { is_complete_ = true; }
  void CompleteEmpty() override { is_complete_ = true; }
 private:
  TestPlatformMessageResponse() = default;
};

@cbracken cbracken Aug 10, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

the constructor should be made private.

Good bot. Done.

Additionally, the unused parameter data in Complete can be omitted to avoid potential compiler warnings about unused parameters.

🤦‍♂️ Bad bot. No it can't. This is an override of a pure virtual method in the flutter::PlatformMessageResponse abstract base class. It's literally marked override. We don't exactly have the option to change the signature. You're right removing the param won't complain about unused parameters... because it won't even compile 😂.

Also, we explicitly compile with -Wno-unused-parameter, so there's no way we'd get those warnings even if this were legit feedback, which it isn't. You can see this yourself in compile_commands.json.

Adds tests for two behaviours the embedder API migration has to
preserve, both at the layer a plugin or the framework actually sees
rather than the layer the current implementation happens to use:

First is GPU availability:

Currently, `createShell:` re-derives `isGpuDisabled` from the live view
controller or application state immediately before creating the shell,
and clobbers whatever the property was set to beforehand. This is an
easy thing to get wrong later: the "obvious" handling via `FlutterEngineRun`
would be to take the initial GPU availability as a project argument, but
passing the property straight through would resurrect exactly the stale
value this code goes out of its way to drop.

Second is Task queues:

`PlatformMessageHandlerIosTest` covers `PlatformMessageHandlerIos`
behaviour on its own, but nothing covered the path a plugin takes to
reach it, through
* `-[FlutterEngine makeBackgroundTaskQueue]` ->
* `-[FlutterEngine setMessageHandlerOnChannel:binaryMessageHandler:taskQueue:]`

`EmbedderPlatformMessageHandler` always trampolines to the platform
thread, so both halves of that contract will have to be rebuilt
explicitly for embedder API migration.

The GPU test locks in the current behaviour. I'll address the TODO in
the next patch to clean up the API. But this is a nice way to
document/verify the behavioural diff in the tests.

Issue: flutter#112232
Issue: flutter#190835
@cbracken
cbracken force-pushed the test-task-queue-behaviour branch from ed75e86 to 2278d88 Compare August 10, 2026 13:10
@cbracken
cbracken added this pull request to the merge queue Aug 10, 2026
Merged via the queue into flutter:master with commit 91ce4de Aug 11, 2026
16 of 17 checks passed
@cbracken
cbracken deleted the test-task-queue-behaviour branch August 11, 2026 01:07
cbracken added a commit to cbracken/flutter that referenced this pull request Aug 13, 2026
Previously, a value assigned to `isGpuDisabled` before the engine ran was
silently discarded: `createShell:` sampled the current lifecycle state by
assigning to the public property immediately before creating the shell, so
the caller's value never reached `Shell::Create`.

The sampling is necessary — lifecycle notifications only report transitions,
so an engine created while the app is already backgrounded never receives
one — but it belongs where its input changes, not where the shell is built.
We now sample when the engine is created and when a view controller is
attached or detached, and `createShell:` passes `_isGpuDisabled` through
untouched. For an app the observable behaviour is unchanged, but a pre-run
assignment now survives to the shell and is corrected by the next real
lifecycle transition rather than by a write the caller cannot see.

Sampling also has to cope with not being able to tell. An app extension has no
`UIApplication` to consult, and reaches the scene only through an attached view
controller whose view is in a window — which neither `setViewController:` call
site has yet. That is also the only configuration in which
`setUpLifecycleNotifications:` registers the scene observers, so treating
"cannot tell" as "in the foreground" would let an attach undo a
`sceneDidEnterBackground:` and re-enable the GPU in the background, which is
what gets an app killed. Where neither source is readable we leave
`isGpuDisabled` as it stands, the last observed transition being the better
answer.

The test added in flutter#190836 asserted the
old behaviour and is inverted here, so this lands as a behavioural diff.

Fixes: flutter#190835
Issue: flutter#112232
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-ios iOS applications specifically team-ios Owned by iOS platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants