-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
QueuedInterceptor is flawed, which causes dead lock by design. #1612
Comments
Built a hacky workaround by separating the retry logic to a following normal interceptor.
So after the change, the @override
Future<void> onError(
DioError err,
ErrorInterceptorHandler handler,
) async {
// ... code unrelated ...
final requestOptions = error.requestOptions;
_logger.info('Schedule retry');
requestOptions._markNeedRetry(retryRequestOptions);
handler.next(err);
// Code causes deadlock
// _logger.info('Retry request');
// dio.fetch(retryRequestOption).then(
// handler.resolve,
// onError: (error) => handler.reject(error as DioError),
// );
} So the helper methods and
const _retryRequestOptionKey = 'retryRequestOption';
extension RequestOptionsExtension on RequestOptions {
void _markNeedRetry(RequestOptions retryRequestOption) {
extra[_retryRequestOptionKey] = retryRequestOption;
}
RequestOptions? _checkNeedRetry() {
final result = extra[_retryRequestOptionKey] as RequestOptions?;
extra.remove(_retryRequestOptionKey);
return result;
}
}
class AuthRetryInterceptor extends Interceptor {
final Dio dio;
AuthRetryInterceptor(this.dio);
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
final retryRequestOptions = err.requestOptions._checkNeedRetry();
if (retryRequestOptions == null) {
return handler.next(err);
}
_logger.info('Retry request');
dio.fetch(retryRequestOptions).then(
handler.resolve,
onError: (error) => handler.reject(error as DioError),
);
}
} And this is dio.addInterceptors([
authInterceptor,
AuthRetryInterceptor(dio),
]); |
Usually this is solved by using another (plain) instance of the Dio client which does not have the same interceptor to do the token refreshing or whatever calls you need to do before you exit the |
Yes. A proper solution was like @kostadin-damyanov-prime mentioned using another |
Hey @kostadin-damyanov-prime @AlexV525, it seems you get the issue wrong: The dead lock DOESN'T happen on the token refreshing but on RETRYING. Once the error happens, whole network stack would be deadlocked, until you recreate the whole dio instance. If dio is hold as singleton by the app, it pretty much means user have to kill the whole app, or any requests sent by this dio will be blocked. As the deadlock happens in retry, where you would like to have the same interceptor chain. So using another |
@timnew I see. I think the issue is caused by the following:
If you take a look at this code posted by the PRs author you will see they use Note: I think the linked code example has another isue, which is tha tit ONLY uses
|
@kostadin-damyanov-prime Thanks for the detailed explanation, I see what you mean. @override
Future<void> onError(
DioError err,
ErrorInterceptorHandler handler,
) async {
// ... code unrelated ...
final requestOptions = error.requestOptions;
// do things requires look
final newToken = await _refreshToken(); // Await to block this method.
]
// retry in a unblock way
_retryRequest(requestOptions._markAsRetry(), handler).ignore(); // Retry in a new microtask and mark it as ignored.
}
Future<void> _retryRequest(
RequestOptions retryRequestOption,
ErrorInterceptorHandler handler,
) async {
_logger.info('Retry request');
await dio.fetch(retryRequestOption).then(
handler.resolve,
onError: (error) => handler.reject(error as DioError),
);
} |
To be honest, I feel
|
Something like this would be enough to prevent the parallel call. Completer<String>? _refreshTokenCompleter;
Future<String> refreshToken() {
if(_refreshTokenCompleter != null) return _refreshTokenCompleter!.future;
final completer = _refreshTokenCompleter = Completer();
try {
final token = await _refreshToken();
completer.complete(token);
return token;
} catch (ex, stacktrace) {
completer.completeError(ex, stacktrace);
rethrow;
}
}
Future<String> _refreshToken() {
// do actual refresh token logic here
} |
Hello @kostadin-damyanov-prime, you mentioned Note: I think the linked code example has another issue, which is that it ONLY uses then and no await at all. This is problematic in the case where there are multiple requests at once and the token needs to be refreshed. I am not sure steps mentioned in the note will still work in case of multiple requests. Once say 3 concurrent requests get queued in the QueuedInterceptor on token expiry, & first one using
I just started flutter a month ago and spent last week looking for the right solution for this. let me know if what I have written is ok or not. |
I am still facing the same issue while making retry. any update on how its done??? @timnew |
I just simply give up the |
@timnew workaround with the completer made the trick for me. |
@timnew would you mind sharing you're full refresh implementation ? Why do you think |
New Issue Checklist
Issue Info
Issue Description and Steps
I uses
QueuedInterceptor
to handle access token, very standard scenario:Authorization
header inonRequest
accessToken
if server responds401
inonError
401
after retried.Code is very similar to official example: https://github.com/flutterchina/dio/blob/develop/example/lib/queued_interceptor_crsftoken.dart
As the code performs an retry as following code shown:
Unfortunately, this code won't work, and it naturally causes dead lock by design:
fetch
on the samedio
instance beforeonError
is finished.QueuedInterceptor
works, calls toonError
will be queued, next one won't be called until current one finished.onError
requests, but it won't be processed because currentonError
is still running.onError
won't finish, as it dependsdio.fetch
to return, which requiresonError
returns first.I might be wrong, but it seems there is no solution to this bug, and
InterceptorLock
is marked as deprecated, so no idea how this would be done properly.The text was updated successfully, but these errors were encountered: