Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HttpClient] Request stream seems still being occupied even if the HttpClientRequest has been aborted #54392

Open
AlexV525 opened this issue Dec 18, 2023 · 6 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-_http library-io P3 A lower priority bug or feature request triaged Issue has been triaged by sub team

Comments

@AlexV525
Copy link
Contributor

This tracker is for issues related to: dart:io.

The following issue can be easily reproduced on Windows.

Dart SDK version: 3.2.3 (stable) (Tue Dec 5 17:58:33 2023 +0000) on "windows_x64"

Reproducible code:

test('delete file when write socket exception', () async {
  final f = File('test/mock/flutter.png'); // Any files can do this.
  final client = HttpClient();
  final request = await client.openUrl(
    'put',
    Uri.parse('https://httpbun.com/put'),
  );
  try {
    final requestStream = f.openRead();
    final future = request.addStream(requestStream); // Set a breakpoint here.
    await future.catchError((e, s) {
      request.abort(e, s);
      print('Aborted');
      return future;
    });
  } catch (e, s) {
    print('$e\n$s');
    await request.flush();
    f.deleteSync();
    rethrow;
  }
});

When the debugger stops at the breakpoint, disconnect the network and continue. The exception will thrown:

Testing started at 9:08 AM ...
The Dart VM service is listening on http://127.0.0.1:60523/KXJ8lfJajgs=/

The Dart DevTools debugger and profiler is available at: http://127.0.0.1:60523/KXJ8lfJajgs=/devtools?uri=ws://127.0.0.1:60523/KXJ8lfJajgs=/ws
Aborted
SocketException: Write failed (OS Error: An existing connection was forcibly closed by the remote host.
, errno = 10054), address = httpbun.com, port = 60900
#0      _NativeSocket.write (dart:io-patch/socket_patch.dart:1246:34)
#1      _RawSocket.write (dart:io-patch/socket_patch.dart:2004:15)
#2      _ExternalBuffer.readToSocket (dart:io/secure_socket.dart:1329:26)
#3      _RawSecureSocket._writeSocket (dart:io/secure_socket.dart:1091:16)
#4      _RawSecureSocket._tryFilter (dart:io/secure_socket.dart:1037:13)
<asynchronous suspension>
#5      _RawSecureSocket._secureHandshake (dart:io/secure_socket.dart:928:9)
<asynchronous suspension>
#6      _RawSecureSocket._tryFilter (dart:io/secure_socket.dart:1049:13)
<asynchronous suspension>
dart:io                     FileSystemEntity.deleteSync
test\upload_test.dart 73:9  main.<fn>

PathAccessException: Cannot delete file, path = 'test/mock/flutter.png' (OS Error: The process cannot access the file because it is being used by another process.
, errno = 32)


Process finished with exit code 1
@AlexV525
Copy link
Contributor Author

@AlexV525
Copy link
Contributor Author

20231218_120453.mp4

@parlough parlough added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-io library-_http labels Dec 18, 2023
@brianquinlan brianquinlan added triaged Issue has been triaged by sub team P3 A lower priority bug or feature request labels Dec 18, 2023
@brianquinlan
Copy link
Contributor

Hmmm...the likely cause is that the stream is not being closed if an exception occurs.

@introvert
Copy link

The same issue appears to us, making it impossible to launch or even debug the app written in Flutter.

Surprised that there aren't more reports - perhaps Flutter has not really gotten on serious use yet?

@AlexV525
Copy link
Contributor Author

AlexV525 commented Jan 8, 2024

The same issue appears to us, making it impossible to launch or even debug the app written in Flutter.

You can try destroying the HttpClient instead of the request, or make copies of files into a temporary directory and ignore them if the deletion fails.

Surprised that there aren't more reports - perhaps Flutter has not really gotten on serious use yet?

Arguing with frameworks is not interesting, we should focus on the problem.

@NightFeather0615
Copy link

NightFeather0615 commented Mar 22, 2024

Greetings! I've facing the same problem when solving issue in localsend/localsend and I found a few solution for this.

The simplest solution is using Stream.asBroadcastStream() to cancel the file stream when there are no subscribers,

const fileName = "some/file/path";

final file = File(fileName);
final fileStream = file.openRead().asBroadcastStream();

final client = HttpClient();

try {
  final request = await client.postUrl(
    Uri.parse('https://httpbun.com/post'),
  );
  final future = request.addStream(fileStream);
  await future.catchError((e, s) {
    request.abort(e, s);
    debugPrint("Aborted");
    return future;
  });
} catch (_) {
  await file.delete(); // Still throws `PathAccessException` because the file stream has not yet canceled
  
  // Using the `retry` package may solve the problem, but it's not the best approach in this case
}

but the downside is that you have to wait until the broadcast stream cancels itself, so here's a more complex but manageable way to do it,

const fileName = "some/file/path";

final file = File(fileName);
final fileStream = file.openRead();
final fileReadController = StreamController<List<int>>();
final fileReadSubscription = fileStream.listen(
  fileReadController.add,
  onError: fileReadController.addError,
  onDone: () => fileReadController.close()
);

final client = HttpClient();

try {
  final request = await client.postUrl(
    Uri.parse('https://httpbun.com/post'),
  );
  final future = request.addStream(fileReadController.stream);
  await future.catchError((e, s) {
    request.abort(e, s);
    debugPrint("Aborted");
    return future;
  });
} catch (_) {
  await fileReadSubscription.cancel(); // Cancel manually to delete files right away
  await file.delete();
}

which is basically just extracting the implementation of the AsBroadcastStream class.

That's my solution, please let me know if there are any problems or there is a better solution.

By the way, I think it's good to mention this in the API docs, as it seems that a lot of people are experiencing this problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-_http library-io P3 A lower priority bug or feature request triaged Issue has been triaged by sub team
Projects
None yet
Development

No branches or pull requests

5 participants