Skip to content

[Impeller] SamplerLibrary caches are read and written from two threads without a lock (Flutter GPU) #193737

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 with a ThreadSanitizer engine (tools/gn ... --tsan).

Expected results

Flutter GPU and Impeller can request samplers at the same time without a race.

Actual results

GetSampler scans the plain samplers_ vector and then calls push_back, with no lock. Flutter GPU calls it on the UI thread (RenderPass.bindTexture), and Impeller calls it on the raster thread (for example FramebufferBlendContents::Render). A scan can read a buffer that push_back is reallocating. Two inserts at once can corrupt the vector or drop a sampler that was already returned.

  • Metal (macOS): TSan reports the race in the code sample in 4 of 6 runs on master.
  • All three backends: an engine test that requests samplers from four threads fails or crashes in 39 or more of 40 runs on Metal, Vulkan (SwiftShader, Linux) and OpenGL ES (Mesa, Linux). TSan reports the race in SamplerLibraryMTL, SamplerLibraryVK and SamplerLibraryGLES.
  • Vulkan and OpenGL ES are confirmed only with that test, not with an app.

We have not seen it crash a release app. It is a sibling of the pipeline library race in #193721 (PR #193722).

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

Logs

TSan, code sample on master d03768e (macOS)
WARNING: ThreadSanitizer: data race
  Read by main thread:
    #0 std::vector<pair<uint64_t, shared_ptr<const Sampler>>>::end()
    #1 impeller::SamplerLibraryMTL::GetSampler()
    #2 BindTextureBinding()
    #3 InternalFlutterGpu_RenderPass_BindTextureIndexed
  Previous write by thread T6 (raster):
    #4 std::vector<pair<uint64_t, shared_ptr<const Sampler>>>::push_back()
    #5 impeller::SamplerLibraryMTL::GetSampler()
    #6 impeller::FramebufferBlendContents::Render()

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
    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
  3. github-actions commented on Oct 25, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  4. locked as resolved and limited conversation to collaborators on Oct 25, 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

    e: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.team-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