Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ See the [Migration Guide][] for the complete breaking changes list.**
on an empty response body when a custom `responseDecoder` is set. It now
returns an empty result, consistent with `SyncTransformer` and
`BackgroundTransformer`.
- Fix concurrent requests hanging or reporting uncaught errors when an
interceptor shares a failing Future, such as in request deduplication.

## 5.10.0

Expand Down
55 changes: 19 additions & 36 deletions dio/lib/src/dio_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -426,34 +426,10 @@ abstract class DioMixin implements Dio {
}
}

// Create an error zone that catches uncaught async errors from
// interceptor callbacks. This prevents requests from hanging when
// an async interceptor throws without calling the handler.
// Synchronous exceptions are not caught here and propagate normally.
// If the handler was already completed (e.g. a fire-and-forget future
// inside the callback failed), forward the error to the parent zone
// so it isn't silently swallowed.
Zone createInterceptorZone(
_BaseHandler handler,
void Function(Object error, StackTrace stackTrace) onUncaughtError,
) {
return Zone.current.fork(
specification: ZoneSpecification(
handleUncaughtError: (self, parent, zone, error, stackTrace) {
if (!handler.isCompleted) {
onUncaughtError(error, stackTrace);
} else {
parent.handleUncaughtError(zone, error, stackTrace);
}
},
),
);
}

// Convert the request interceptor to a functional callback in which
// we can handle the return value of interceptor callback.
FutureOr Function(dynamic) requestInterceptorWrapper(
InterceptorSendCallback cb,
_InterceptorCallback<RequestOptions, RequestInterceptorHandler> cb,
) {
return (dynamic incomingState) {
final state = incomingState as InterceptorState;
Expand All @@ -462,13 +438,15 @@ abstract class DioMixin implements Dio {
requestOptions.cancelToken,
Future(() async {
final handler = RequestInterceptorHandler();
createInterceptorZone(
final result = cb(state.data as RequestOptions, handler);
_observeInterceptorCallback(
result,
handler,
(error, stackTrace) => handler.reject(
assureDioException(error, requestOptions, stackTrace),
true,
),
).run(() => cb(state.data as RequestOptions, handler));
);
return handler.future;
}),
);
Expand All @@ -480,7 +458,7 @@ abstract class DioMixin implements Dio {
// Convert the response interceptor to a functional callback in which
// we can handle the return value of interceptor callback.
FutureOr<dynamic> Function(dynamic) responseInterceptorWrapper(
InterceptorSuccessCallback cb,
_InterceptorCallback<Response<dynamic>, ResponseInterceptorHandler> cb,
) {
return (dynamic incomingState) {
final state = incomingState as InterceptorState;
Expand All @@ -490,13 +468,15 @@ abstract class DioMixin implements Dio {
requestOptions.cancelToken,
Future(() async {
final handler = ResponseInterceptorHandler();
createInterceptorZone(
final result = cb(state.data as Response, handler);
_observeInterceptorCallback(
result,
handler,
(error, stackTrace) => handler.reject(
assureDioException(error, requestOptions, stackTrace),
true,
),
).run(() => cb(state.data as Response, handler));
);
return handler.future;
}),
);
Expand All @@ -508,20 +488,22 @@ abstract class DioMixin implements Dio {
// Convert the error interceptor to a functional callback in which
// we can handle the return value of interceptor callback.
FutureOr<dynamic> Function(Object) errorInterceptorWrapper(
InterceptorErrorCallback cb,
_InterceptorCallback<DioException, ErrorInterceptorHandler> cb,
) {
return (dynamic error) {
final state = error is InterceptorState
? error
: InterceptorState(assureDioException(error, requestOptions));
Future<InterceptorState> handleError() async {
final handler = ErrorInterceptorHandler();
createInterceptorZone(
final result = cb(state.data, handler);
_observeInterceptorCallback(
result,
handler,
(error, stackTrace) => handler.next(
assureDioException(error, requestOptions, stackTrace),
),
).run(() => cb(state.data, handler));
);
return handler.future;
}

Expand Down Expand Up @@ -552,7 +534,7 @@ abstract class DioMixin implements Dio {
for (final interceptor in interceptors) {
final fun = interceptor is QueuedInterceptor
? interceptor._handleRequest
: interceptor.onRequest;
: interceptor._invokeRequest;
future = future.then(requestInterceptorWrapper(fun));
}

Expand All @@ -569,22 +551,23 @@ abstract class DioMixin implements Dio {
} on DioException catch (e) {
handler.reject(e, true);
}
return null;
}),
);

// Add response interceptors into the request flow
for (final interceptor in interceptors) {
final fun = interceptor is QueuedInterceptor
? interceptor._handleResponse
: interceptor.onResponse;
: interceptor._invokeResponse;
future = future.then(responseInterceptorWrapper(fun));
}

// Add error handlers into the request flow.
for (final interceptor in interceptors) {
final fun = interceptor is QueuedInterceptor
? interceptor._handleError
: interceptor.onError;
: interceptor._invokeError;
future = future.catchError(errorInterceptorWrapper(fun));
}
// Normalize errors, converts errors to [DioException].
Expand Down
Loading
Loading