-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to reproduce
- Create new Flutter project:
flutter create bug && cd bug - Add a dependency on
pkg:vector_math:flutter pub add vector_math(dependency onvector_mathis almost certainly not needed but it makes the repro code cleaner) - Replace
lib/main.dartwith the code below (in code sample section). - Run in any non-web target (e.g.
flutter run -d macosorflutter build macos) to ensure there is no memory leak (memory stays at a certain level):
- Run on the web (e.g.
flutter build web) in any configuration (release / profile / debug, wasm or not, etc.)
Expected results
Used memory doesn't grow.
Actual results
Memory used by the web app goes up linearly at several megabytes per second:
After a couple of minutes, the memory consumed reaches 1.0 GB and keeps growing:
I tried debugging this through Dart DevTools but Dart DevTools don't have access to memory info for web apps. All I can do is use Chrome DevTools. I can't say I understand what I'm looking at in Chrome DevTools, but here are some things I noticed:
- Basically all the RAM is taken by some
JSArrayBufferDataobject:
- This object seems to have something in common with a
flutterCanvasKitobject, and specifically with something calledglobalThis.
Snapshot file from Chrome DevTools: Heap-20240818T130707.heapsnapshot.zip
I initially thought this had something to do with Flame but as you can see from the sample, the same issue appears on vanilla Flutter as well. // cc @zoeyfan
Code sample
Code sample
It's possible that the code could be made simpler but before I invest that kind of work, I'd like to make sure this is actually a bug and that I'm not overlooking something obvious or doing something stupid.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:vector_math/vector_math_64.dart' hide Colors;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
),
body: const Center(
child: MemoryPressureWidget(),
),
);
}
}
class MemoryPressureWidget extends StatefulWidget {
const MemoryPressureWidget({super.key});
@override
State<MemoryPressureWidget> createState() => _MemoryPressureWidgetState();
}
class _MemoryPressureWidgetState extends State<MemoryPressureWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
final List<PairedWanderer> wanderers = [];
@override
Widget build(BuildContext context) {
return Stack(
children: [
for (final wanderer in wanderers)
PairedWandererWidget(wanderer: wanderer),
],
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_createBatch(1000, const Size(500, 500));
_controller = AnimationController(vsync: this);
}
void _createBatch(int batchSize, Size worldSize) {
assert(batchSize.isEven);
final random = Random(42);
for (var i = 0; i < batchSize / 2; i++) {
final a = PairedWanderer(
velocity: (Vector2.random() - Vector2.all(0.5))..scale(100),
worldSize: worldSize,
position: Vector2(worldSize.width * random.nextDouble(),
worldSize.height * random.nextDouble()),
);
final b = PairedWanderer(
velocity: (Vector2.random() - Vector2.all(0.5))..scale(100),
worldSize: worldSize,
position: Vector2(worldSize.width * random.nextDouble(),
worldSize.height * random.nextDouble()),
);
a.otherWanderer = b;
b.otherWanderer = a;
wanderers.add(a);
wanderers.add(b);
}
}
}
class PairedWanderer {
PairedWanderer? otherWanderer;
final Vector2 position;
final Vector2 velocity;
final Size worldSize;
PairedWanderer({
required this.position,
required this.velocity,
required this.worldSize,
});
void update(double dt) {
position.addScaled(velocity, dt);
if (otherWanderer != null) {
position.addScaled(otherWanderer!.velocity, dt * 0.25);
}
if (position.x < 0 && velocity.x < 0) {
velocity.x = -velocity.x;
} else if (position.x > worldSize.width && velocity.x > 0) {
velocity.x = -velocity.x;
}
if (position.y < 0 && velocity.y < 0) {
velocity.y = -velocity.y;
} else if (position.y > worldSize.height && velocity.y > 0) {
velocity.y = -velocity.y;
}
}
}
class PairedWandererWidget extends StatefulWidget {
final PairedWanderer wanderer;
const PairedWandererWidget({required this.wanderer, super.key});
@override
State<PairedWandererWidget> createState() => _PairedWandererWidgetState();
}
class _PairedWandererWidgetState extends State<PairedWandererWidget>
with SingleTickerProviderStateMixin {
late Ticker _ticker;
Duration _lastElapsed = Duration.zero;
@override
void initState() {
super.initState();
_ticker = createTicker(_onTick);
_ticker.start();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Positioned(
left: widget.wanderer.position.x - 128 / 4,
top: widget.wanderer.position.y - 128 / 4,
child: const SizedBox(
width: 8,
height: 8,
child: Placeholder(),
),
);
}
void _onTick(Duration elapsed) {
var dt = (elapsed - _lastElapsed).inMicroseconds / 1000000;
dt = min(dt, 1 / 60);
widget.wanderer.update(dt);
_lastElapsed = elapsed;
setState(() {});
}
}Screenshots or Video
Screenshots / Video demonstration
The code sample is compatible with Dartpad.
Logs
Logs
[Paste your logs here]Flutter Doctor output
Doctor output
flutter doctor -v
[✓] Flutter (Channel stable, 3.24.0, on macOS 14.4.1 23E224 darwin-arm64,
locale en-US)
• Flutter version 3.24.0 on channel stable at
/Users/filiph/fvm/versions/stable
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 80c2e84975 (3 weeks ago), 2024-07-30 23:06:49 +0700
• Engine revision b8800d88be
• Dart version 3.5.0
• DevTools version 2.37.2
[✓] Android toolchain - develop for Android devices (Android SDK version
35.0.0)
• Android SDK at /Users/filiph/Library/Android/sdk
• Platform android-35, build-tools 35.0.0
• Java binary at: /Applications/Android
Studio.app/Contents/jbr/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build
17.0.7+0-17.0.7b1000.6-10550314)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 15F31d
• CocoaPods version 1.15.0
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2023.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build
17.0.7+0-17.0.7b1000.6-10550314)
[✓] IntelliJ IDEA Ultimate Edition (version 2023.3.2)
• IntelliJ at /Users/filiph/Applications/IntelliJ IDEA Ultimate.app
• Flutter plugin version 78.4.2
• Dart plugin version 233.15271
[✓] VS Code (version 1.87.2)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.84.0
[✓] Connected device (3 available)
• macOS (desktop) • macos • darwin-arm64
• macOS 14.4.1 23E224 darwin-arm64
• Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin
• macOS 14.4.1 23E224 darwin-arm64
• Chrome (web) • chrome • web-javascript
• Google Chrome 127.0.6533.120
[✓] Network resources
• All expected network resources are available.
• No issues found!



