dio icon indicating copy to clipboard operation
dio copied to clipboard

QueuedInterceptors strange behavior

Open bustazone opened this issue 1 month ago • 1 comments

Package

dio

Version

5.9.0

Operating-System

iOS

Adapter

Default Dio

Output of flutter doctor -v

[✓] Flutter (Channel stable, 3.35.0, on macOS 14.7.1 23H222 darwin-arm64, locale es-ES) [3,4s]
    • Flutter version 3.35.0 on channel stable at /Users/javierbustamante/fvm/versions/3.35.0
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision b896255557 (3 months ago), 2025-08-13 17:14:08 -0700
    • Engine revision 1e9a811bf8
    • Dart version 3.9.0
    • DevTools version 2.48.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations,
      enable-lldb-debugging

[!] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [12,9s]
    • Android SDK at /Users/javierbustamante/Library/Android/sdk
    • Emulator version 35.2.10.0 (build_id 12414864) (CL:N/A)
    • Platform android-36, build-tools 35.0.0
    • Java binary at: /Users/javierbustamante/Library/Java/JavaVirtualMachines/jbr-17.0.12/Contents/Home/bin/java
      This JDK is specified in your Flutter configuration.
      To change the current JDK, run: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment JBR-17.0.12+1-1207.37-nomod (build 17.0.12+1-b1207.37)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

[✓] Xcode - develop for iOS and macOS (Xcode 16.2) [13,9s]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16C5032a
    • CocoaPods version 1.16.2

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

[✓] Android Studio (version 2024.2) [9ms]
    • 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 21.0.3+-79915917-b509.11)

[✓] VS Code (version 1.104.3) [7ms]
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension can be installed from:
      🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] Connected device (3 available) [13,1s]
    • iPhone 16 Pro (mobile) • A4547058-A9C0-4194-816F-8EAC302CD150 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-18-2 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 14.7.1 23H222 darwin-arm64
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 142.0.7444.162
    ! Error: Browsing on the local area network for iPhone de Busta. Ensure the device is unlocked and attached with a cable or associated with
      the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)

[✓] Network resources [308ms]
    • All expected network resources are available.

! Doctor found issues in 1 category.

Dart Version

3.9.0

Steps to Reproduce

I've found a problem with the performance of the QueuedInterceptors. I have a really complex interceptors distribution so it's easier to describe the problem with an example. I'd like to clarify that maybe it's no a bug but a lack of clear documentation.

Let's see the case:

To start, I have 4 interceptors (I1, I2, I3, I4) and I passed it to dio with: instance.interceptors.addAll([I1, I2, I3, I4]); Each interceptor is a queued inteceptor (beaceause the order of execution is very important) and has different functionality in onResponse and onError. So the Idea is to chain the Interceptors with the following order:

OutGoing: I1(onRequest) -> I2(onRequest) -> I3(onRequest) -> I4(onRequest) Incoming: I4(onResponse|onError) -> I3(onResponse|onError) -> I2(onResponse|onError) -> I1(onResponse|onError)

The goal is that each interceptor get the state of the previous one, i.e.: if the onResponse function of I3 returns a handler.reject it has to be followed by the execution of the I2 onError function.

Here comes the problems:

  • Actually the outgoing order is good I1(onRequest) -> I2(onRequest) -> I3(onRequest) -> I4(onRequest) but when the call incomes from network I see the same execution order (I1(onRequest) -> I2(onRequest) -> I3(onRequest) -> I4(onRequest)).
  • The other problem is that when I execute a handler.reject the call returns an exception directly instead of execute the onError of the next Interceptor.

In order to clarify, that the dummy code for the test:

Test interceptor:

import 'package:dio/dio.dart';

class DioNetTestErrorInterceptor extends QueuedInterceptor {
  final String id;
  DioNetTestErrorInterceptor(this.id);

  @override
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    print('[DioNetTestErrorInterceptor-$id] onRequest');
    return handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) async {
    print('[DioNetTestErrorInterceptor-$id] onResponse');

    if (id == '4') {
      final dioError = DioException(
        requestOptions: response.requestOptions,
        error: 'Fake error',
        type: DioExceptionType.badResponse,
        response: Response(
          requestOptions: response.requestOptions,
          statusCode: 500,
          data: {"code": 900, "message": "Fake error"},
        ),
      );
      return handler.reject(dioError);
    } else {
      return handler.next(response);
    }
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print('[DioNetTestErrorInterceptor-$id] onError');
    return handler.next(err);
  }
}

Dio instance creation:

Dio newInstance = Dio(options);
newInstance.interceptors.addAll([
	DioNetTestErrorInterceptor(1),
	DioNetTestErrorInterceptor(2),
	DioNetTestErrorInterceptor(3),
	DioNetTestErrorInterceptor(4),
])

Expected Result

Start Call [DioNetTestErrorInterceptor-1] onRequest [DioNetTestErrorInterceptor-2] onRequest [DioNetTestErrorInterceptor-3] onRequest [DioNetTestErrorInterceptor-4] onRequest [DioNetTestErrorInterceptor-4] onResponse [DioNetTestErrorInterceptor-3] onError [DioNetTestErrorInterceptor-2] onError [DioNetTestErrorInterceptor-1] onError End Call

Actual Result

Start Call [DioNetTestErrorInterceptor-1] onRequest [DioNetTestErrorInterceptor-2] onRequest [DioNetTestErrorInterceptor-3] onRequest [DioNetTestErrorInterceptor-4] onRequest [DioNetTestErrorInterceptor-1] onResponse End Call

bustazone avatar Nov 20 '25 08:11 bustazone

  1. Queued interceptors are meant to be FIFO, so 1234 -> 4321 does not follow any of the regular sequences. Maybe you'll need to aggregate your interceptors as a chain externally.
  2. There is no way to specify callFollowingErrorInterceptor when calling reject in an error interceptor. I'll look into it and see if we can expose the behavior or not.

AlexV525 avatar Nov 24 '25 03:11 AlexV525