dio icon indicating copy to clipboard operation
dio copied to clipboard

Add QueuedInterceptor instead of interceptors.lock

Open wendux opened this issue 2 years ago • 33 comments

What is QueuedInterceptor?

As we all know, we can intercept requests/responses/errors before they are handled by then or catchError with dio Interceptor。In previous versions, interceptors could be executed concurrently, that is, all of the requests enter the interceptor at once, rather than executing sequentially. However, in some cases we expect that requests enter the interceptor sequentially like #590 。 Therefore, we need to provide a mechanism for sequential access(one by one) to interceptors and QueuedInterceptor is proposed to solve this problem.

Examples

We make 3 concurrent requests, all of which enter the interceptor at the same time when using Interceptor; But when using QueuedInterceptor , requests enter the interceptor sequentially.

void main() async {
  var dio = Dio();
  dio.options.baseUrl = 'http://httpbin.org/status/';
  dio.interceptors.add(
    InterceptorsWrapper(
      onRequest: (
        RequestOptions requestOptions,
        RequestInterceptorHandler handler,
      ) {
        print(requestOptions.uri);
        Future.delayed(Duration(seconds: 2), () {
          handler.next(requestOptions);
        });
      },
    ),
  );
  print( 'All of the requests enter the interceptor at once, rather than executing sequentially.');
  await makeRequests(dio);

  print( 'All of the requests enter the interceptor sequentially by QueuedInterceptors');
  dio.interceptors
    ..clear()
    ..add(
      // [QueuedInterceptorsWrapper] is a helper class, which is used to conveniently create QueuedInterceptor.
      QueuedInterceptorsWrapper( 
        onRequest: (
          RequestOptions requestOptions,
          RequestInterceptorHandler handler,
        ) {
          print(requestOptions.uri);
          Future.delayed(Duration(seconds: 2), () {
            handler.next(requestOptions);
          });
        },
      ),
    );
  await makeRequests(dio);
}

Future makeRequests(Dio dio) async {
  try {
    await Future.wait([
      dio.get('/200'),
      dio.get('/201'),
      dio.get('/201'),
    ]);
  } catch (e) {
    print(e);
  }
}

Of course, you can implement your own queued interceptor directly by inheriting from QueuedInterceptor.

Delete Locks of interceptors

Locks of interceptors were originally designed to synchronize interceptor execution, but locks have a problem that once it becomes unlocked all of the requests run at once, rather than executing sequentially. Now QueuedInterceptor can do it better.

To delete Lock another reason is that Lock is shared between the interceptors, so Lock can be called by any interceptors at any place, it will bring potential risks, assuming a interceptor invokes lock, but because the code defects, lead to can't normal call unLock, this will affect the rest of the interceptor to normal work (may never be executed to).

How to try QueuedInterceptor.

Use 4.0.2-beta1 or change to develop branch.

wendux avatar Oct 31 '21 12:10 wendux

It works for me! 💯

akifarhan avatar Nov 18 '21 07:11 akifarhan

Why do you deprecate locks? Could you provide example of token refreshment? In that case you need lock only for one part of the interceptor. (In the case where you repeat request)

AlexanderFarkas avatar Nov 29 '21 16:11 AlexanderFarkas

Could you provide migration guide for requestLock and responseLock. It's not really clear to me how to replace it.

comatory avatar Dec 14 '21 13:12 comatory

requestLock and responseLock can be removed. They were used to prevent concurrent interceptors which is now handled by QueuedInterceptor

RumoneAnderson avatar Dec 14 '21 19:12 RumoneAnderson

@RumoneAnderson So by simply removing and replacing InterceptorWrapper with QueuedInterceptorsWrapper it's guaranteed the behaviour will be the same?

comatory avatar Dec 14 '21 20:12 comatory

Being a guarantee that it will work for you, not sure. However, I have tested it and it works as expected.

RumoneAnderson avatar Dec 15 '21 07:12 RumoneAnderson

How to clear a QueuedInterceptor's queue for cases when auth token unable to be refreshed? in 4.0.4 dio.clear() method are only clears lock's queue

orteney avatar Dec 18 '21 17:12 orteney

We use the locks to deal with expired tokens and I'm not too comfortable changing this without good documentation.

Could someone please provide a concrete migration example for this?

aboutandre avatar Dec 19 '21 00:12 aboutandre

How to clear a QueuedInterceptor's queue for cases when auth token unable to be refreshed? in 4.0.4 dio.clear() method are only clears lock's queue

this case cannot be handled currently with QueuedInterceptor. before with could clear unlock QueuedInterceptor holds ques internally no way to reset.

after token refresh failed interceptor becames inactive no error or result and we getting loading with no result.

fullflash avatar Dec 20 '21 18:12 fullflash

with this new changes. what is the solution for refreshing tokens ?

mosi2142 avatar Dec 24 '21 07:12 mosi2142

Why do you deprecate locks? Could you provide example of token refreshment? In that case you need lock only for one part of the interceptor. (In the case where you repeat request)

A example for refreshing tokens with QueuedInterceptor : https://github.com/flutterchina/dio/blob/develop/example/lib/queued_interceptor_crsftoken.dart

wendux avatar Dec 27 '21 07:12 wendux

How do I lock/unlock the requests?

TercyoStorck avatar Jan 08 '22 19:01 TercyoStorck

It looks not so good to me, in my scenario.

I am using requestLock in my project for temporarily locking a dio instance and using another instance (with different Cookie jars, so they have to be held separately) to request a token for the former.

Since the token could be expired at any time, I have to check every response of the first dio and decide whether it is necessary to get a new token and replay the request. In that case, with requestLock I can simply lock the first, request a new token and unlock it.

But it isn't so easy to me when using a QueuedInterceptor. When should the requests of the first dio be blocked? It seems much harder to implement the function.

w568w avatar Jan 20 '22 13:01 w568w

I am using requestLock in my project for temporarily locking a dio instance and using another instance (with different Cookie jars, so they have to be held separately) to request a token for the former one.

I'd like to know this as well. My code contains multiple dio instances, some of them are set up differently. At the moment I can decide which ones to lock/unlock etc.

comatory avatar Jan 20 '22 13:01 comatory

Is sample auth interceptor https://gist.github.com/TimurMukhortov/a1c9819e3779015e54bc3964b7d2308a for dio. its work for me.

TimurMukhortov avatar Feb 16 '22 18:02 TimurMukhortov

@wendux

l. My code contains multiple dio instances, some of them are set up differently. At the moment I can decide which ones to lock/unlock etc.

There is a problem with this implementation code, if an error is reported when dio.fetch() is called in onError, the error will not be passed to the outside. Here is sample code: https://github.com/ipcjs/dio/pull/1

ipcjs avatar Mar 01 '22 12:03 ipcjs

I don't understand the need to deprecate the .clear() method. There is a chance someone might want to clean the waiting queue even if they are using QueuedInterceptor implementation.

For e.g. There might be repeated duplicate requests to the same endpoint, while the token is being refreshed. All of them get inserted in the queue, ready to run after the token is refreshed. However, after it is done, I might only want to execute the original request once, with the new token, and remove all the other duplicate requests. How is it possible to achieve this without clearing the queue?

arafaysaleem avatar Apr 03 '22 00:04 arafaysaleem

@wendux

l. My code contains multiple dio instances, some of them are set up differently. At the moment I can decide which ones to lock/unlock etc.

There is a problem with this implementation code, if an error is reported when dio.fetch() is called in onError, the error will not be passed to the outside. Here is sample code: ipcjs#1

using with legacy it work properly but with QueuedInterceptorsWrapper not.

the problem is at line 428 if (!taskQueue.processing) { return true but taskQueue.queue is empty it get stucks. Screen Shot 2022-04-04 at 01 33 26

fullflash avatar Apr 03 '22 20:04 fullflash

@fullfash I have create a PR(#1457 ) to fix the queued_interceptor_crsftoken bug.

The key point to note about using QueuedInterceptor is that the current dio instance is already blocked and another dio instance needs to be used to execute the request.

ipcjs avatar Apr 03 '22 21:04 ipcjs

@ipcjs but same situation can be happen on second dio instance also.

fullflash avatar Apr 03 '22 21:04 fullflash

Don't add a QueuedInterceptor to the second dio instance, so it won't be blocked. 😅

ipcjs avatar Apr 03 '22 21:04 ipcjs

yes but wondering if there can be any leaking case. 🤔 on repeat request there might be also a token expiration very small chance but... there must be an error prone fix for QueuedInterceptor logic.

anyway thanks . we will use tokenDio for repeat request also

fullflash avatar Apr 03 '22 21:04 fullflash

I don't understand the need to deprecate the .clear() method. There is a chance someone might want to clean the waiting queue even if they are using QueuedInterceptor implementation.

For e.g. There might be repeated duplicate requests to the same endpoint, while the token is being refreshed. All of them get inserted in the queue, ready to run after the token is refreshed. However, after it is done, I might only want to execute the original request once, with the new token, and remove all the other duplicate requests. How is it possible to achieve this without clearing the queue?

@wendux @ipcjs can somebody clear this for me?

arafaysaleem avatar Apr 03 '22 22:04 arafaysaleem

@fullflash you are right. maybe we need add a repeatDio, like this: https://github.com/ipcjs/dio/commit/dfe102d5bed4da72e8f77f8f67395ba2b7d47b40 🤔️

ipcjs avatar Apr 03 '22 22:04 ipcjs

@ipcjs this is a good workaround. but i am still trying find a fix for QueuedInterceptor. there should be url mapping for overriding duplicate url request queues and that can fix the stuck on repeated dio calls.

fullflash avatar Apr 03 '22 22:04 fullflash

@ipcjs our repeated request error bug is about QueuedInterceptors error handler.

i came we this patch: to keep active processing que and force complete when same request path and data repeats.

if (taskQueue.activeQueue != null &&
        taskQueue.processing &&
        handler is ErrorInterceptorHandler) {
      final DioError cur = data as DioError;
      final DioError a = taskQueue.activeQueue!.data! as DioError;
      if (a.requestOptions.path == cur.requestOptions.path &&
          a.requestOptions.data == cur.requestOptions.data) {
        // taskQueue.processing = false;
        final ErrorInterceptorHandler activeErrHandler =
            taskQueue.activeQueue!.handler;
        activeErrHandler.next(taskQueue.activeQueue!.data);
      }
    }

this bypasses your test. Will use such hardcoded way of dio package and make some tests to be sure.

fullflash avatar Apr 04 '22 00:04 fullflash

To modify the lib you need to ask the maintainer of the project. @wendux @kuhnroyal I think it is better to modify the example.

ipcjs avatar Apr 04 '22 06:04 ipcjs

How to clear the queue? I got refreshing token happening 5 times to refresh the token. Is there a way to clear the queue?

iamchathu avatar Jun 03 '22 05:06 iamchathu

Thank @ipcjs for your suggestion.

About this sample https://github.com/flutterchina/dio/blob/dfe102d5bed4da72e8f77f8f67395ba2b7d47b40/example/lib/queued_interceptor_crsftoken.dart

If you use another Dio instance to repeat the request (line 107 and line 83), everything should be working.

For this new Dio instance, don't forget to add your QueuedInterceptorsWrapper interceptor too.

Dio library does not seem to be maintained anymore ... does it?

antoinepemeja avatar Jun 15 '22 15:06 antoinepemeja

Thank @ipcjs for your suggestion.

About this sample https://github.com/flutterchina/dio/blob/dfe102d5bed4da72e8f77f8f67395ba2b7d47b40/example/lib/queued_interceptor_crsftoken.dart

If you use another Dio instance to repeat the request (line 107 and line 83), everything should be working.

For this new Dio instance, don't forget to add your QueuedInterceptorsWrapper interceptor too.

Dio library does not seem to be maintained anymore ... does it?

unfortunately it looks abandoned. need alternative for "no hello world" applications )

Zead Is Dead Baby https://www.youtube.com/watch?v=zgGFK5drCpk

fullflash avatar Jun 17 '22 01:06 fullflash