Skip to content

Connection closed before response was received #2170

Closed

Description

@Abouassi98

Package

dio

Version

5.4.2+1

Operating-System

Android, iOS

Adapter

Default Dio

Output of flutter doctor -v

[✓] Flutter (Channel stable, 3.19.4, on macOS 14.4.1 23E224 darwin-arm64, locale en-EG)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.2)
[✓] VS Code (version 1.87.2)
[✓] Connected device (3 available)            
    ! Error: Browsing on the local area network for Me. 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)
    ! Error: Browsing on the local area network for Mohamed’s Iphone (2). 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)
    ! Error: Browsing on the local area network for ‏iPhone ‏Fadwa. 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)
    ! Error: Browsing on the local area network for H. Sayed. 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

• No issues found!

Dart Version

3.3.2

Steps to Reproduce

  1. Run with any version of Dio above 5.4.0.
  2. try to send more requests in a short period of time.

Expected Result

When using Dio version 5.4.0, the connection is not closed and the response succeeds.

untitled2

Actual Result

flutter: The following exception was thrown getBOKRechargeRequestStatusProvider:
flutter: AppException.serverException(type: ServerExceptionType.unknown, message: HttpException: Connection closed before response was received, uri = https://api.bravo-sudan.com/bravo/api/v1/bok/getRechargeRequests, code: null)
flutter:
#0 DioMixin.fetch (package:dio/src/dio_mixin.dart:509:7)

#1 MainApiFacade._errorHandler (package:bravo/core/infrastructure/network/main_api/api_callers/main_api_facade.dart:66:14)

#2 TransactionRemoteDataSource.getBOKRechargeRequestStatus (package:bravo/features/transaction/infrastructure/data_sourse/transaction_remote_remote_data_source.dart:48:20)

#3 TransactionHistoryRepository.getBOKRechargeRequestStatus (package:bravo/features/transaction/infrastructure/repository/transaction_history_repository.dart:43:20)

untitled

Activity

  1. AlexV525 commented on Mar 28, 2024

    @AlexV525
    Member

    If this is version specific, could you run git bisect to find out which commit breaks the functionality?

  2. Abouassi98 commented on Mar 28, 2024

    @Abouassi98
    Author

    I have recently upgraded Dio from version 5.4.0 to 5.4.2+1. However, I believe the issue began with version 5.4.1.

  3. AlexV525 commented on Mar 28, 2024

    @AlexV525
    Member

    Running bisect will help to determine which exact commit breaks the behavior.

  4. Abouassi98 commented on Mar 28, 2024

    @Abouassi98
    Author

    I am contemplating running a bisect to pinpoint the problematic commit. However, I want to clarify that my recent commit solely consisted of upgrading the version to 5.4.2+1. Additionally, I have already tested version 5.4.1, as I suspect the issue originated there, and encountered the same problem.

  5. AlexV525 commented on Mar 28, 2024

    @AlexV525
    Member

    I understand your statement clearly, IMO. I'm asking you to do the bisect because you have a reproducible example and can debug the issue easily. Besides, simply switching between versions might not be helpful since every version includes a bunch of commits.

  6. Abouassi98 commented on Mar 28, 2024

    @Abouassi98
    Author

    I've completed running git bisect to identify the commit that introduced the issue in the Dio package. After testing multiple commits, I've found that the first bad bisect commit is "Fix streamed response cancellation and progress (#2068)".

    845e06f

  7. AlexV525 commented on Mar 29, 2024

    @AlexV525
    Member

    I've completed running git bisect to identify the commit that introduced the issue in the Dio package. After testing multiple commits, I've found that the first bad bisect commit is "Fix streamed response cancellation and progress (#2068)".

    845e06f

    Thanks for the bisect! Now could you split a minimal reproducible example so we can take a deeper investigation with your case?

  8. added
    p: dioTargeting `dio` package
    and removed
    h: need triageThis issue needs to be categorized
    on Mar 29, 2024
  9. AlexV525 commented on Mar 29, 2024

    @AlexV525
    Member

    cc @kuhnroyal to check the implementation as the code owner

  10. 6 remaining items

  11. added
    valuableThe issue or the fix means a lot to the library.
    and removed on Apr 3, 2024
  12. EArminjon commented on Apr 5, 2024

    @EArminjon

    What is the recommandation to avoid this issue ?

  13. kuhnroyal commented on Apr 5, 2024

    @kuhnroyal
    Member

    This was changed in 5.4.1, you might be able to lock the dio version to 5.4.0.

  14. devguusta commented on Apr 8, 2024

    @devguusta
  15. wratheus commented on Apr 10, 2024

    @wratheus

    The issue occurs when you have two requests with different CancelTokens, and when the first request is completed, the second request starts executing. At that moment, if you cancel the CancelToken of the first request somewhere in the code (for example, in Bloc dispose), then the second request gets canceled before it is received, even if it had a different cancelToken from the first request, causing the entire DIO connection to close.

    For example

    List<CancelToken> cancelTokens = [CancelToken(), CancelToken()]
    RequestData request1 = await Repo.request1(cancelTokens.first); // done
    RequestData request2 = Repo.request2(cancelTokens.last);  // no await
    microtask({
        cancelTokens.first.cancel();  // parallel cancel first cancelToken
    });

    While request2 is still being execute, a cancelation occurs on the first CancelToken in parallel in code, which will drop request2. that cancelation should not influence second request.

    Fixes I found so far:

    1. You can use lower Dio version (I rollbacked to 5.3.3, did not checked later versions yet).
    2. Open new Dio connection for each request.
    3. Initialize DIO with (BaseOptions(persistenConnection: false)). I think this option is the same meaning as new dio client for new request.
  16. kuhnroyal commented on Apr 10, 2024

    @kuhnroyal
    Member

    @wratheus Can you reproduce this in a test case? Also not sure if this is the same issue.

    This seems to work:

        test('cancel one request', () {
          final dio = Dio()..options.baseUrl = 'https://httpbun.com';
          final token = CancelToken();
    
          expectLater(
            dio.get(
              '/drip?delay=0&duration=2&numbytes=10',
              cancelToken: token,
            ),
            throwsDioException(DioExceptionType.cancel),
          );
          expectLater(
            dio.get(
              '/drip?delay=0&duration=2&numbytes=10',
              cancelToken: CancelToken(),
            ),
            completes,
          );
    
          Future.delayed(const Duration(seconds: 1), () {
            token.cancel('cancelled');
          });
        });
  17. AlexV525 commented on Apr 11, 2024

    @AlexV525
    Member

    This case fails.

    test(
    'not closing sockets with requests that have same hosts',
    () async {
    final token = CancelToken();
    final completer = Completer<Response?>();
    // Complete the first request with the cancel token.
    await dio.get('/get', cancelToken: token);
    // Request the second without any cancel token, but with the same host.
    dio.get('/drip?duration=3').then(
    (res) {
    completer.complete(res);
    return res;
    },
    onError: (e) {
    completer.complete(null);
    return Response(requestOptions: (e as DioException).requestOptions);
    },
    );
    // Simulate connection established.
    await Future.delayed(const Duration(seconds: 1));
    token.cancel();
    final response = await completer.future;
    // Response should be obtained without exceptions.
    expect(response, isNotNull);
    },
    testOn: '!browser',
    );

  18. wratheus commented on Apr 11, 2024

    @wratheus

    There is no difference if there is cancelToken in second request or no. Same with post requests. That's the problem I was talking about. Issue does not occurs at 5.3.3 version

    import 'dart:async';
    
    import 'package:dio/dio.dart';
    import 'package:flutter_test/flutter_test.dart';
    
      void main() {
        Dio dio = Dio();
        test(
          'not closing sockets with requests that have same hosts',
              () async {
            final token = CancelToken();
            const String baseUrl = 'https://httpbun.com';
    
            final completer = Completer<Response?>();
            // Complete the first request with the cancel token.
            await dio.get('$baseUrl/get', cancelToken: token);
            // Request the second without any cancel token, but with the same host.
            dio.get('$baseUrl/drip?duration=3', cancelToken: CancelToken()).then(
                  (res) {
                completer.complete(res);
                return res;
              },
              onError: (e, t) {
                    print(e);
                    print(t);
                completer.complete(null);
                return Response(requestOptions: (e as DioException).requestOptions);
              },
            );
            // Simulate connection established.
            await Future.delayed(const Duration(seconds: 1));
            token.cancel();
            final response = await completer.future;
            // Response should be obtained without exceptions.
            expect(response, isNotNull);
          },
          testOn: '!browser',
        );
      }
  19. added a commit that references this issue on Apr 11, 2024
    6656bd5
  20. chesongsong commented on Jun 24, 2024

    @chesongsong

    5.4.0

    Is there a big difference between 5.4.0 and 5.4.2+1? I also reproduced this problem on 5.4.2+1. If I lock 5.4.0, does my user need to make API level adjustments?

  21. AlexV525 commented on Jun 24, 2024

    @AlexV525
    Member

    The fix is included in v5.4.3 and shall not require interface updates.

  22. added a commit that references this issue on Aug 31, 2026
    6368c16
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

    b: regressionWorked before but not nowfixedp: dioTargeting `dio` packageplatform: ios: bugSomething isn't workingvaluableThe issue or the fix means a lot to the library.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions