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

Some Async Coroutine Functions #431

Closed
wants to merge 10 commits into from
Closed

Some Async Coroutine Functions #431

wants to merge 10 commits into from

Conversation

iNoles
Copy link
Collaborator

@iNoles iNoles commented Sep 14, 2018

Thank you for submitting your Pull Request. Please make sure you have
familiarised yourself with the Contributing Guidelines
before continuing.

Description

I added async block for some functions and left blocking one alone. I was a bit curious why somebody else didn't added this one.

Fixes #429

Type of change

Check all that apply

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactoring (change which changes the current internal or external interface)
  • This change requires a documentation update

How Has This Been Tested?

In case you did not include tests describe why you and how you have verified the
changes, with instructions so we can reproduce. If you have added comprehensive
tests for your changes, you may ommit this section.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation, if necessary
  • My changes generate no new compiler warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@iNoles iNoles changed the title Some coroutine functions are under async now. Some Async Coroutine Functions Sep 14, 2018
@SleeplessByte
Copy link
Collaborator

I'm personally not a fan of the double await. asyncObjectResponse().await() would work, and allows you to have this be additive and not breaking.

@iNoles
Copy link
Collaborator Author

iNoles commented Sep 14, 2018

@SleeplessByte Yeah, I see what you mean.

Added Async coroutine functions
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.suspendCancellableCoroutine
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.experimental.*
Copy link
Collaborator

Choose a reason for hiding this comment

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

would you mind reverting this change as we try not to use wildcard imports

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, I will.

await(byteArrayDeserializer(), scope)
): Triple<Request, Response, Result<ByteArray, FuelError>> = await(byteArrayDeserializer(), scope)

suspend fun Request.asyncByteArrayResponse(
Copy link
Collaborator

Choose a reason for hiding this comment

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

is there a reason for changing the name of this function?

Copy link
Collaborator Author

@iNoles iNoles Sep 14, 2018

Choose a reason for hiding this comment

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

It looks like await on next line.

Copy link
Collaborator

Choose a reason for hiding this comment

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

sorry I see that

@@ -23,7 +23,7 @@ class CoroutinesTest {
@Test
fun testAwaitResponseSuccess() = runBlocking {
try {
Fuel.get("/ip").awaitByteArrayResponse().third.fold({ data ->
Copy link
Owner

Choose a reason for hiding this comment

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

Don't we want to keep awaitXX version for our user to use? Are we gonna deprecated them and move user to use the asyncXX version instead? I think either way we probably want to keep the tests for awaitXXX

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah definitely keep the tests until the await versions are removed, if ever.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh Okay, I will restore awaitXX versions of tests.

@kittinunf
Copy link
Owner

kittinunf commented Sep 15, 2018

I think this looks great. It almost like we should deprecate the awaitXX version and expose only asyncXX. Because it looks like expose it as a Deferred triumphs other versions in every way 💦

All in all, after this PR is merged our Coroutine API landscape will look like this.

Type\Return * Result<*, FuelError> Triple<Request, Response, Result<*, FuelError> Deferred<Triple<Request, Response, Result<*, FuelError>>>
ByteArray awaitByteArray awaitByteArrayResult awaitByteArrayResponse asyncByteArrayResponse
String awaitString awaitStringResult awaitStringResponse asyncStringResponse
T awaitObject awaitObjectResult awaitObjectResponse asyncObjectResponse

It looks a bit wild.

I think it might be "good" to provide the same treatment (provide the Deferred<> version) with the rest of the available API. 😅

But this means that we are going to double the available API for our users. .... 🙀

I almost feel like, at this point, we might wanna clean this all up at once. Might it be just wiser to return the Deferred<> version instead?

What do you think?

@iNoles
Copy link
Collaborator Author

iNoles commented Sep 15, 2018

@kittinunf I would love to deprecated Result and Triple and only uses Deferred.

@SleeplessByte
Copy link
Collaborator

SleeplessByte commented Sep 15, 2018 via email

@kittinunf
Copy link
Owner

kittinunf commented Sep 16, 2018

Just to confirm, I guess we want to go with just 2 versions!

Type\Return Deferred<Result<*, FuelError>> Deferred<Triple<Request, Response, Result<*, FuelError>>>
ByteArray asyncByteArrayResult asyncByteArrayResponse
String asyncStringResult asyncStringResponse
T asyncObjectResult asyncObjectResponse

And the rest, we shall deprecate them?

@iNoles
Copy link
Collaborator Author

iNoles commented Sep 16, 2018

@kittinunf
that would be like

async {
asyncByteArrayResponse().await().third
}

You don't need to have async on that line.

@kittinunf
Copy link
Owner

Hmm, in my experience, Result<*, FuelError> is actually being used around 80% of my time, the rest about 20%, I need to check for HTTP status code, that is where the Triple version becomes handy.

@iNoles If you don't mind, could you help me out?

@iNoles
Copy link
Collaborator Author

iNoles commented Sep 17, 2018

@kittinunf Sure
I would go this route

fun awaitByteArrayResult(): Result<ByteArray, FuelError> = asyncByteArrayResponse().await().third

@kittinunf
Copy link
Owner

kittinunf commented Sep 17, 2018

Hmm, I think it is supposed to be Deferred<Result<T, FuelError>> ...

This is for the parity of the APIs so that asyncXXXResponse return Deferred<Triple<>> while asyncXXXResult return Deferred<Result<>>

Do you think it makes sense?

@SleeplessByte
Copy link
Collaborator

SleeplessByte commented Sep 17, 2018 via email

@iNoles
Copy link
Collaborator Author

iNoles commented Sep 17, 2018

@SleeplessByte Deferred's await() will wait for Deferred to complete.

@kittinunf I have a concern about it over asyncXXXResult because it is adding more complexity to the developers. It is going to be like asyncXXXResponse().await().third.await(). It going to be Confusing for double await().

@SleeplessByte
Copy link
Collaborator

@iNoles exactly what I thought :) And I agree with the double await being confusing.

@clarfonthey
Copy link

Using a Deferred doesn't actually solve this problem, sadly. It also seems like an anti-pattern because it's easy to just wrap the await in your own async call, whereas this forces you to use the Deferred, which is dispatched on a context you can't control.

The main problem described in #429 is that the coroutine still blocks instead of suspending the thread when it's waiting on I/O. This doesn't actually fix that problem.

@iNoles
Copy link
Collaborator Author

iNoles commented Sep 18, 2018

Using a Deferred doesn't actually solve this problem, sadly. It also seems like an anti-pattern because it's easy to just wrap the await in your own async call, whereas this forces you to use the Deferred, which is dispatched on a context you can't control.

The main problem described in #429 is that the coroutine still blocks instead of suspending the thread when it's waiting on I/O. This doesn't actually fix that problem.

Oh, what does solve this problem?

@markGilchrist
Copy link
Collaborator

@clarcharr @SleeplessByte I am sure that I may well have this wrong, however when I first looked at implementing this I can remember that I felt the best approach to make this a 'true' suspending implementation one would have to change (or implement similar)

line 53, Deserializable.kt

private fun <T : Any, U : Deserializable<T>> Request.response(deserializable: U,
                                                              success: (Request, Response, T) -> Unit,
                                                              failure: (Request, Response, FuelError) -> Unit): Request {
...

as this (if I understand this correctly) is the part that makes the request.

I think that one would have to add a suspending function here that executes the task in order to achieve what you are looking for

@kittinunf
Copy link
Owner

Ahhhh sorry, I understand it now. Then, the current API proposal is OK to me.

@clarcharr I think we need you to perhaps shed some light here. I think the last thing we want is merging this and it doesn't solve your problem.

@iNoles
Copy link
Collaborator Author

iNoles commented Sep 18, 2018

@kittinunf Do you want me to restore

fun awaitByteArrayResult(): Result<ByteArray, FuelError> = asyncByteArrayResponse().await().third

because 80% of the time?

@kittinunf
Copy link
Owner

kittinunf commented Sep 19, 2018

I feel like if I am gonna use the coroutine version, I think I might prefer to use Deferred<Result<T, E> instead. Because I don't use coroutine on my work at all so please take my opinion for a gain of salt.

@clarfonthey
Copy link

I'm not going to be able to get to this today, but I'll try this weekend to draft up the sort of changes that would be needed to fix the original issue. Part of it can be done without really affecting the surface API, but another part can't.

Basically, I think that establishing a connection to the server and getting back the response can be done asynchronously, where the only difference from the existing implementation is that the current thread isn't blocked from performing other work while the connection happens.

Making the read of the response body asynchronous would require changing the deserialization API, which would be breaking. But as far as things go, a majority of the time for the request is in getting back the response headers, which means that there's still a lot to gain without breaking the API.

@markGilchrist
Copy link
Collaborator

@SleeplessByte @iNoles @clarcharr I would really value your critique on my pr #438

@lucasvsme
Copy link

Just a note about returning a Deferred<T>: https://stackoverflow.com/questions/49762153/modify-the-deferred-result/49762498#49762498

@kittinunf kittinunf added the ⚠️ work in progress Under construction. Do not merge. This is not final. label Sep 26, 2018
@codecov
Copy link

codecov bot commented Sep 29, 2018

Codecov Report

Merging #431 into master will decrease coverage by 0.99%.
The diff coverage is 0%.

Impacted file tree graph

@@           Coverage Diff            @@
##             master    #431   +/-   ##
========================================
- Coverage      77.8%   76.8%   -1%     
  Complexity      164     164           
========================================
  Files            33      33           
  Lines           811     815    +4     
  Branches        129     135    +6     
========================================
- Hits            631     626    -5     
- Misses          114     129   +15     
+ Partials         66      60    -6
Impacted Files Coverage Δ Complexity Δ
...com/github/kittinunf/fuel/coroutines/Coroutines.kt 9.67% <0%> (-19.96%) 0 <0> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3393d38...c7d6291. Read the comment docs.

@iNoles iNoles self-assigned this Sep 30, 2018
@iNoles
Copy link
Collaborator Author

iNoles commented Oct 4, 2018

I am going to close this PR because async-await is BEST for Multiple coroutines with concurrency. Since 80% of the cases, it going to be single operation that is best for withContext.

@iNoles iNoles closed this Oct 4, 2018
@iNoles iNoles deleted the AsyncCoroutines branch October 4, 2018 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚠️ work in progress Under construction. Do not merge. This is not final.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Actually asynchronous coroutines
6 participants