Skip to content

Cancelling a ResponseType.stream subscription leaves the adapter response subscribed #2616

Closed as not planned

Description

@kaluli123123

Package

dio

Version

5.11.1; reproduced on master 4684e29d with the repository package.

Operating-System

MacOS (Dart VM regression). The reproduction uses a local fake adapter and does not require an external server.

Adapter

The response-stream handler shared by adapters; reproduced through the public Dio API with a streaming test adapter.

Output of flutter doctor -v

Host environment (identifiers redacted)
[!] Flutter (Channel stable, 3.44.7, on macOS 26.6.2 25G83 darwin-arm64, locale zh-Hans-TW) [515ms]
    • Flutter version 3.44.7 on channel stable at <home>/fvm/versions/3.44.7
    ! Warning: `flutter` on your path resolves to <home>/Documents/sdk/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at <home>/fvm/versions/3.44.7. Consider adding <home>/fvm/versions/3.44.7/bin to the front of your path.
    ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/3.12.2/libexec/bin/dart, which is not inside your current Flutter SDK checkout at <home>/fvm/versions/3.44.7. Consider adding <home>/fvm/versions/3.44.7/bin to the front of your path.
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 84fc5cbb22 (3 months ago), 2026-07-17 10:20:17 -0700
    • Engine revision 69c8c61792
    • Dart version 3.12.2
    • DevTools version 2.57.0
    • Pub download mirror https://pub.flutter-io.cn
    • Flutter download mirror https://storage.flutter-io.cn
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration
    • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades.

[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [1,616ms]
    • Android SDK at <home>/Library/Android/sdk
    • Emulator version 36.6.11.0 (build_id 15507667) (CL:N/A)
    • Platform android-36.1, build-tools 36.1.0
    • ANDROID_HOME = <home>/Library/Android/sdk
    • ANDROID_SDK_ROOT = <home>/Library/Android/sdk
    • 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) [931ms]
    • 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.17.0

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

[!] Proxy Configuration [3ms]
    • HTTP_PROXY is set
    ! NO_PROXY is not set

[✓] Connected devices
    • Device names and identifiers redacted.

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

! Doctor found issues in 3 categories.

Dart Version

3.12.2 stable, macos_arm64.

Steps to Reproduce

Consume one event using response.data!.stream.first, or cancel its subscription, without cancelling the request's optional CancelToken. The consumer is finished but the wrapper keeps its subscription to the adapter response.

import 'dart:async';
import 'dart:typed_data';
import 'package:dio/dio.dart';

class StreamingAdapter implements HttpClientAdapter {
  StreamingAdapter(this.source);
  final StreamController<Uint8List> source;
  @override
  Future<ResponseBody> fetch(RequestOptions options,
      Stream<Uint8List>? requestStream, Future<void>? cancelFuture) async =>
      ResponseBody(source.stream, 200);
  @override
  void close({bool force = false}) {}
}

Future<void> main() async {
  var cancelCount = 0;
  final source = StreamController<Uint8List>(onCancel: () => cancelCount++);
  final dio = Dio()..httpClientAdapter = StreamingAdapter(source);
  final response = await dio.get<ResponseBody>(
    'https://example.test/stream',
    options: Options(responseType: ResponseType.stream),
  );
  final first = response.data!.stream.first;
  source.add(Uint8List.fromList([1]));
  print(await first);
  print('source.hasListener: ${source.hasListener}, cancelCount: $cancelCount');
  // Actual: true, 0. Expected: false, 1.
  unawaited(source.close());
  dio.close();
}

Expected Result

Consumer cancellation should cancel the source response subscription, release adapter resources, stop receive-progress/timeout work, and complete its cancellation future after asynchronous source cleanup finishes.

Actual Result

The source remains subscribed. handleResponseStream propagates pause/resume but has no consumer onCancel cleanup. Normal stream completion, explicit CancelToken cancellation, and timeout handling are separate existing paths.

Four new public-entry regressions fail against unchanged production code: .first, direct cancellation, pause followed by cancellation, and waiting for asynchronous upstream cleanup. The first three observe source.hasListener == true; the last finds that cleanup did not start. This can leave streaming responses active after a page stops consuming them. No claim of measured battery, TCP, or production-memory impact is made.

A focused fix and regression coverage are being prepared. The earlier CancelToken fixes and pause/resume work are different cancellation boundaries.

Activity

  1. github-actions commented on Oct 4, 2026

    @github-actions
    Contributor

    @kaluli123123 Infra and blank issues are only available for moderators. You're apparently using the wrong issue template.

    Infra 和空白 issue 仅供管理人员使用,请选择其他 issue 模板创建 issue。

  2. locked as off topic and limited conversation to collaborators on Oct 4, 2026
  3. added a commit that references this issue on Oct 4, 2026
    0477132
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions