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

Fix future type to allow returning null in catchError #4

Merged
merged 8 commits into from
Feb 3, 2023

Conversation

davidmartos96
Copy link
Contributor

Hello!
I've recently encountered with an issue when awaiting the close of a PostgresPool
After some debugging I found that the issue comes from the usage of catchError + null return when joining the tasks.

futures.add(item.result.future.catchError((_) async => null));

The type of the task is R, but that doesn't mean it can accept null as a type, so it fails at runtime with the following error.
Invalid argument(s) (onError): The error handler of Future.catchError must return a value of the future's type

Here is a test that breaks in current version of executor.

        final executor = Executor(concurrency: 2);
        for (var i = 0; i < 10; i++) {
          // Non-nullable int as a type
          // This mimics the usage of executor in postgres_pool, 
          // which are also non-nullable types
          executor.scheduleTask<int>(() async {
            await Future.delayed(Duration(microseconds: i * 10));
            await Future<Null>.microtask(() => throw Exception());

            // Some arbitrary value to match the task type
            return 1;
          });
        }
        await executor.join(withWaiting: true);
        await executor.close();

This PR adds a few tests to check for non-null and nullable task types behavior and fixes the issue.

Copy link
Contributor

@isoos isoos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely convinced that the right fix is the current one in the PR. Would you please try out the List<Object?> return type as suggested, whether it is still an issue with postgres?

return await item.result.future
// Nullable R is used to allow using catchError with null output, so
// we must convert R? into R for the caller
.then((v) => v ?? v as R);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like something that shouldn't really happen, or rather: something that we shouldn't write. I'd think (v) => v as R would be sufficient, but then it would be a no-op, wouldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result.future is R? so removing the then would not compile. I've updated the code with simply v as R

@@ -119,8 +122,8 @@ class _Executor implements Executor {
}

@override
Future join({bool withWaiting = false}) {
final futures = <Future>[];
Future<void> join({bool withWaiting = false}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method ends with Future.wait which returns with a list:
https://api.dart.dev/stable/2.19.1/dart-async/Future/wait.html

Maybe the package:postgres issue comes from the lack of type annotation here, and the return type should be Future<List<Object?>> instead. It seems to be better to expose the return type here, than to make type casts in the schedule, which 3-6 months from now would seem rather mysterious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I leave that to your decision. I typed it as void because the output of join is not used.
The function could also be marked as async and instead of returning the future we can do await Future.wait(...) so that the output is void

@davidmartos96
Copy link
Contributor Author

I'm not entirely convinced that the right fix is the current one in the PR. Would you please try out the List<Object?> return type as suggested, whether it is still an issue with postgres?

I don't think it's a problem with postgres_pool, rather the pool it's using the executor with non-null typed tasks, which when dealing with catchErrors, fail

About the List<Object?>, would you like to make that the output type for join?

@isoos
Copy link
Contributor

isoos commented Feb 3, 2023

About the List<Object?>, would you like to make that the output type for join?

I think that is the key here: we need to return a list, otherwise it would be a breaking change (not on the explicit API, but in behavior). It also has a high chance that this is all we need to fix the issue you have encountered, and revert the changes in the other parts (notably the then call).

@isoos isoos merged commit 0ca2170 into agilord:master Feb 3, 2023
@isoos
Copy link
Contributor

isoos commented Feb 3, 2023

published, thank you for the change and the patient explanation!

@davidmartos96
Copy link
Contributor Author

Thank you! The issue was kind of hard to explain with words 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants