Skip to content

[Impeller] Metal: PipelineLibraryMTL pipeline map is accessed from two threads without a lock (crash with Flutter GPU) #193721

Closed

Description

@leehack

Steps to reproduce

  1. Create a macOS app from the code sample below and enable Flutter GPU (FLTEnableFlutterGPU in Info.plist).
  2. Run it. A Flutter GPU scene draws every frame while the overlay keeps creating new Impeller pipeline variants (blurred text shadow, backdrop blur, changing blend mode).

A normal run crashes only rarely. With a ThreadSanitizer engine (tools/gn ... --tsan) the race is reported within seconds.

Expected results

Flutter GPU and Impeller can request pipelines at the same time without a race, as on Vulkan, where PipelineLibraryVK locks its maps.

Actual results

PipelineLibraryMTL reads and writes pipelines_ with no lock (still true on master). Flutter GPU looks pipelines up on the UI thread (RenderPass::Draw → GetPipeline) while the raster thread inserts new variants (ContentContext → CreateVariant → GetPipeline), so a lookup can hit a rehash.

  • We hit it once in a release app on 3.47.1: SIGSEGV in unordered_map::find, while the raster thread was building a pipeline for the first blurred text shadow.
  • TSan reports the race in the code sample on master.
  • An engine test that looks pipelines up while two threads add new ones fails, hangs or crashes without a lock, and passes 40/40 with one.

SamplerLibraryMTL and PipelineLibraryGLES look similar and will be reported separately.

Code sample

pubspec.yaml and lib/main.dart

pubspec.yaml:

name: gpu_pipeline_race
publish_to: none

environment:
  sdk: ^3.13.0

dependencies:
  flutter:
    sdk: flutter
  flutter_scene: 0.23.0
  vector_math: ^2.4.3

flutter:
  uses-material-design: true

lib/main.dart:

import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_scene/scene.dart';
import 'package:vector_math/vector_math.dart' as vm;

void main() => runApp(const MaterialApp(home: Repro()));

class Repro extends StatefulWidget {
  const Repro({super.key});

  @override
  State<Repro> createState() => _ReproState();
}

class _ReproState extends State<Repro> with SingleTickerProviderStateMixin {
  final Scene scene = Scene();
  late final Ticker ticker;
  int frame = 0;

  @override
  void initState() {
    super.initState();
    final geometry = CuboidGeometry(vm.Vector3.all(0.4));
    final materials = [UnlitMaterial(), PhysicallyBasedMaterial()];
    for (var i = 0; i < 400; i++) {
      scene.add(
        Node(mesh: Mesh(geometry, materials[i % 2]))
          ..localTransform = vm.Matrix4.translation(
            vm.Vector3((i % 20 - 10) * 0.6, 0, (i ~/ 20 - 10) * 0.6),
          ),
      );
    }
    ticker = createTicker((_) => setState(() => frame++))..start();
  }

  @override
  void dispose() {
    ticker.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final mode = BlendMode.values[frame % BlendMode.values.length];
    return Stack(
      children: [
        Positioned.fill(
          child: SceneView(
            scene,
            camera: PerspectiveCamera(
              position: vm.Vector3(0, 10, 14),
              target: vm.Vector3.zero(),
            ),
          ),
        ),
        Positioned(
          left: 20,
          top: 20,
          width: 300,
          height: 200,
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(Colors.teal, mode),
            child: ClipRect(
              child: BackdropFilter(
                filter: ui.ImageFilter.blur(
                  sigmaX: 1.0 + frame % 20,
                  sigmaY: 1.0 + frame % 20,
                ),
                blendMode: mode,
                child: Text(
                  'frame $frame',
                  style: TextStyle(
                    fontSize: 40,
                    color: Colors.white,
                    shadows: [
                      Shadow(blurRadius: 1.0 + frame % 40, color: Colors.black),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

Screenshots or Video

N/A (crash).

Logs

Crash and TSan stacks

Release crash, 3.47.1 (EXC_BAD_ACCESS at 0x3f800000):

Thread 0 Crashed (main / UI thread):
  std::unordered_map<PipelineDescriptor, PipelineFuture<...>>::find
  impeller::PipelineLibraryMTL::GetPipeline
  flutter::gpu::RenderPass::GetOrCreatePipeline
  flutter::gpu::RenderPass::Draw
Thread 6 (io.flutter.raster), same moment:
  -[AGXG16XFamilyDevice newRenderPipelineStateWithDescriptor:completionHandler:]
  impeller::PipelineLibraryMTL::GetPipeline
  impeller::Pipeline<PipelineDescriptor>::CreateVariant
  impeller::ContentContext::GetTexturePipeline
  impeller::GaussianBlurFilterContents::RenderFilter
  impeller::TextShadowCache::Lookup

TSan, code sample on master d03768e:

WARNING: ThreadSanitizer: data race
  Read by main thread:
    __hash_table::find
    impeller::PipelineLibraryMTL::GetPipeline
    flutter::gpu::RenderPass::GetOrCreatePipeline
  Previous write by thread T7 (raster):
    __hash_table::__emplace_unique_key_args
    impeller::PipelineLibraryMTL::GetPipeline
    impeller::Pipeline<PipelineDescriptor>::CreateVariant
    impeller::ContentContext::GetTexturePipeline

Flutter Doctor output

flutter doctor -v
[✓] Flutter (Channel stable, 3.47.1, on macOS 26.7 25G229 darwin-arm64, locale en-CA) [1,710ms]
    • Flutter version 3.47.1 on channel stable at /Users/<user>/development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 6655482ec0 (6 weeks ago), 2026-08-19 10:07:23 -0700
    • Engine revision 5d53178869
    • Dart version 3.13.1
    • DevTools version 2.60.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, enable-record-use, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration

[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [4.4s]
    • Android SDK at /Users/<user>/Library/Android/sdk
    • Emulator version 36.6.11.0 (build_id 15507667) (CL:N/A)
    • Platform android-36.1, build-tools 36.1.0
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.10+-117844308-b1163.108)
    • All Android licenses accepted.

[!] Xcode - develop for iOS and macOS (Xcode 27.0) [4.5s]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 27A266a
    ! iOS 27.0 Simulator not installed; this may be necessary for iOS and macOS development.
      To download and install the platform, open Xcode, select Xcode > Settings > Components,
      and click the GET button for the required platform.

      For more information, please visit:
        https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [7ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (3 available) [7.3s]
    • iPhone 17 Pro (mobile) • <simulator-udid> • ios            • com.apple.CoreSimulator.SimRuntime.iOS-26-4 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 26.7 25G229 darwin-arm64
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 154.0.8037.95

[✓] Network resources [1,093ms]
    • All expected network resources are available.

! Doctor found issues in 1 category.

Activity

  1. added
    c: crashStack traces logged to the console
    engineflutter/engine related. See also e: labels.
    e: impellerImpeller rendering backend issues and features requests
    on Oct 3, 2026
  2. added and removed on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: crashStack traces logged to the consolee: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.found in release: 3.47Found to occur in 3.47team-fluttergpuOwned by Flutter GPU team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions