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

Make await a template #12085

Merged
merged 5 commits into from
Apr 25, 2020
Merged

Make await a template #12085

merged 5 commits into from
Apr 25, 2020

Conversation

alehander92
Copy link
Contributor

@alehander92 alehander92 commented Aug 29, 2019

Adapted status-im/nim-chronos#47 for asyncdispatch with support for multisync : credit for the original impl to @yglukhov !

@alehander92
Copy link
Contributor Author

it also helps #11911 (comment) and adding stuff like waitFor check

@alehander92 alehander92 requested a review from dom96 August 29, 2019 11:01
@Araq
Copy link
Member

Araq commented Aug 29, 2019

What does that do? Enable await within templates?

@alehander92
Copy link
Contributor Author

Yes, now i can use it in templates : but it also makes await a template which is a cleaner implementation compared to checking for idents (and one can easily implement other stuff like waitFor is defined out of {.async.}, but not inside)

dom96
dom96 previously requested changes Sep 2, 2019
Copy link
Contributor

@dom96 dom96 left a comment

Choose a reason for hiding this comment

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

This is cool, but the async macro still has code which processes await. You should remove that code to make sure your template is actually being used.

@alehander92 alehander92 force-pushed the await-template branch 3 times, most recently from ed04b24 to 6bd3a38 Compare September 28, 2019 09:19
@dom96
Copy link
Contributor

dom96 commented Apr 13, 2020

Any updates on this?

@alehander92
Copy link
Contributor Author

alehander92 commented Apr 13, 2020

sorry, i left some comments but not sure if easily visible: it doesnt work for some reason with T : something like "expr has no type" errors, it really doesnt seem obvious to me why

this is for the review task to use T instead of auto

@dom96
Copy link
Contributor

dom96 commented Apr 13, 2020

That's fine, but there are other comments that need resolving.

@alehander92
Copy link
Contributor Author

ok, i'll try to push a fix for those

@alehander92 alehander92 force-pushed the await-template branch 5 times, most recently from c89dc26 to 873cd78 Compare April 16, 2020 13:50
@alehander92
Copy link
Contributor Author

ok, the only remaining issue seems to be that tasyncawait_cyclebreaker.nim requires me to increase the limit from 1000 to ~150_000 which .. seems strange, have to debug how i break it

@alehander92
Copy link
Contributor Author

alehander92 commented Apr 16, 2020

EDIT: ok code fixed (dont merge yet: wanted to remove some code)

otherwise Araq said https://irclogs.nim-lang.org/16-04-2020.html#13:51:11 so i'll leave the test as that (even though i think it might be leaking as it constantly deallocates less)

@alehander92 alehander92 force-pushed the await-template branch 2 times, most recently from 937ef93 to 58ebdb9 Compare April 16, 2020 15:19
@alehander92
Copy link
Contributor Author

ok, now it should be ready, also used error instead of assert after i saw a subsequent @yglukhov PR to chronos status-im/nim-chronos#48

@alehander92 alehander92 requested a review from dom96 April 16, 2020 16:28
@alehander92 alehander92 dismissed dom96’s stale review April 16, 2020 16:29

sorry, the changes comment about code processing await seems to be for outdated older changes: please review the fresh ones

Copy link
Contributor

@dom96 dom96 left a comment

Choose a reason for hiding this comment

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

I like these changes a lot, but there are a couple of things that I think still need to be resolved. Sorry about the back and forth.

This time I was very thorough so this should be the last of it.

Comment on lines 331 to 339
template await*(f: untyped): untyped =
when declared(retFuture):
static:
error "await expects Future[T], got " & $typeof(f)
elif not declared(inMultisync):
static:
error "await only available within {.async.}"
else:
f
Copy link
Contributor

Choose a reason for hiding this comment

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

From what I can tell this is only here to allow multisync to work. It took me a while to figure this out, can you add a comment to explain this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, now that I look at the code above, I would just remove this and keep the stripAwait logic in splitProc. Adding this inMultisync variable into the generated code is more hacky. That way you can also reuse the compiler's type mismatch error messages.

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 really think this is also elegant: after all you change how this works quickly with high level code flow without any ast surgery. inMultisync should be somehow optimizeable, its needed only on compile time

Copy link
Contributor

@dom96 dom96 Apr 17, 2020

Choose a reason for hiding this comment

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

A loop which modifies the AST may be more code, but it's also far more deterministic and easier to debug should something go wrong. Hygiene is important and generating variables so that you can then check for their existence is flaky. The current multisync macro does what it needs to do, and then gets out of your way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but .. wouldn't this break templates again? after all you wouldn't be able to generate await in multisync, i think

Copy link
Contributor

Choose a reason for hiding this comment

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

oh, you mean if a template using await is used in a multisync proc? I think you're right. But in that case, can we resolve that in a separate PR? I just like to keep this clean.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, but we really shouldn't get into a situation where just one of the cases is fixed.. they are symmetric

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, no we dont need those variables anyway, so i think this can be still one PR

@@ -257,7 +178,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
outerProcBody.add(prc.body[0])

# -> var retFuture = newFuture[T]()
var retFutureSym = genSym(nskVar, "retFuture")
var retFutureSym = ident"retFuture"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this changed? Without genSym here if someone declares a retFuture of their own then there will be a clash...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in order for await to be able to check for this symbol in when IIRC

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh. Yeah, I'm not the biggest fan of this. If someone accidentally defines a retFuture in their proc then it'll be considered async (that behaviour will be very confusing).

I wonder if we can come up with a better way to detect async procs, can you check if compiles(yield f) works? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

interesting idea, but this would work in another iterator as well ! which we still don't want.

maybe i can generate a symbol which can't be written by users similar to :env

Copy link
Contributor

Choose a reason for hiding this comment

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

but this would work in another iterator as well ! which we still don't want.

IMO it's not the worst thing if it does work, it'll only work if that iterator's return type is a future and if a future is awaited, so likelihood is that it will make sense anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we can do something else : we can just leave out template await and and generate just one in of async: Future[T] working and one in multisync sync: which works for every type: all other cases just fail like normal overloads!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this way we can really just use the default nim mechanisms for most of the errors as well (await just isn't defined somewhere, or it just doesnt accepts int somewhere)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and if we really want to we can still leave a global overload which errors with a more detailed message as a fallback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this works!

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'll push a new commit when i test it a bit

@@ -110,13 +110,15 @@ Async traceback:
## Executes pending callbacks
asyncmacro\.nim\(\d+?\)\s+?fooNimAsyncContinue
## Resumes an async procedure
tasync_traceback\.nim\(\d+?\)\s+?fooIter
asyncmacro\.nim\(\d+?\)\s+?fooIter
Copy link
Contributor

Choose a reason for hiding this comment

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

This actually reduces the amount of signal we get from the async tracebacks. I cannot recall whether this particular line provided useful information, but if it does then that could be a blocker for these changes. Can you run this test and show us what the tracebacks are before and after your changes?

Copy link
Contributor Author

@alehander92 alehander92 Apr 16, 2020

Choose a reason for hiding this comment

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

IIRC the change is similar to what is seen in the diff, basically we need to add the linedir of tasync_traceback to the result of template somehow maybe?

Copy link
Contributor

Choose a reason for hiding this comment

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

I want to see what line the traceback points to now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

asyncmacro.nim(261)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

which seems to be (cast[type(f)](internalTmpFuture)).read()

Copy link
Contributor

Choose a reason for hiding this comment

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

That should be okay, I'm assuming that tasync_traceback in the old traceback doesn't point to anything interesting though.

@alehander92
Copy link
Contributor Author

ok, so now we generate await templates inline to not need those retFuture / inMultisync idents, however this require await to be mixin in templates using await (or to annotate them with {.dirty.})

this can be hinted in an error message tho

@alehander92
Copy link
Contributor Author

alehander92 commented Apr 22, 2020

sorry! it seems we can just skip the global overload fallback with a custom error and then template without annotation dirty can work. not perfect, but i think it's good enough!

Copy link
Contributor

@dom96 dom96 left a comment

Choose a reason for hiding this comment

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

I love it, but please check with @Araq that this won't slow down compilation. Generating so many copies of the same template seems like it could go badly :)

@@ -332,8 +253,24 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
if returnType.kind == nnkEmpty:
# Add Future[void]
result.params[0] = parseExpr("owned(Future[void])")

# based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
Copy link
Contributor

Choose a reason for hiding this comment

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

I would remove this comment, at this point this really doesn't have many parallels. I doubt it'll be useful for those exploring this code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

well, I still got the "define await as a template" overall approach from there, but I am not sure what's the best way to document that stuff

Comment on lines +349 to +352
# overload for await as a fallback handler, based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
# template await*(f: typed): untyped =
# static:
# error "await only available within {.async.}"
Copy link
Contributor

Choose a reason for hiding this comment

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

You still need this, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

oh, you removed this because you didn't want dirty. Is dirty really that bad? :)

Copy link
Contributor Author

@alehander92 alehander92 Apr 23, 2020

Choose a reason for hiding this comment

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

no, but it can be a bit surprising: we need to somehow hint here in the error "if you use a template, you might want to use {.dirty.}" because otherwise many users would be just confused by why just a template/macro can't work

on the other hand just a type mismatch/undefined error is very natural, await is not found and that's it

Comment on lines +322 to +327
result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false)
var multisyncAwait = quote:
template await(value: typed): untyped =
value

result[0][^1] = nnkStmtList.newTree(multisyncAwait, result[0][^1])
Copy link
Contributor

Choose a reason for hiding this comment

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

I love it :D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

credit to @zah's idea #11911 (comment) i think i finally remembered this is also possible

@alehander92
Copy link
Contributor Author

I love it, but please check with @Araq that this won't slow down compilation. Generating so many copies of the same template seems like it could go badly :)

Ok, I was wondering about that as well, maybe the compiler should be able to somehow cache such cases (applicable for other template/macro applications)

@Araq
Copy link
Member

Araq commented Apr 24, 2020

I don't see the copies here, what do you mean?

@alehander92
Copy link
Contributor Author

Araq, it's about inserting two await templates into each async function (and one into each sync generated by multisync)

@Araq
Copy link
Member

Araq commented Apr 25, 2020

I cannot imagine these 2 little templates are noticable, merging.

@Araq Araq merged commit 5658441 into nim-lang:devel Apr 25, 2020
markspanbroek added a commit to codex-storage/asynctest that referenced this pull request Jun 22, 2023
The `check eventually` template is incompatible
with Nim 1.2. It uses `await` in a template,
which is not possible until after 1.2.

nim-lang/Nim#12085 (comment)
markspanbroek added a commit to codex-storage/asynctest that referenced this pull request Jun 22, 2023
The `check eventually` template is incompatible
with Nim 1.2. It uses `await` in a template,
which is not possible until after 1.2.

nim-lang/Nim#12085 (comment)
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.

6 participants