pyscript: async tags are blocking the execution flow

Consider the following case:

    def test_multiple_async(self):
        self.pyscript_run(
        """
        <py-script>
            import js
            import asyncio
            for i in range(3):
                js.console.log('A', i)
                await asyncio.sleep(0.1)
        </py-script>

        <py-script>
            import js
            import asyncio
            for i in range(3):
                js.console.log('B', i)
                await asyncio.sleep(0.1)
        </py-script>

        """)

I would expect it to print A and B lines alternatively, i.e.:

A 0
B 0
A 1
B 1
A 2
B 2

However, pyscript awaits the end of each code tag before commencing to execute the next, so the output is A0 A1 A2 B0 B1 B2. I think the culprit is the await in the following line: https://github.com/pyscript/pyscript/blob/4694602862f86659c803c1f1881ba5d5d0bb4194/pyscriptjs/src/runtime.ts#L161-L165

My gut feeling is that pyscript should probably start all scripts together and then asyncio.wait() for all of them at the end (but we need to check how this interacts with the JS event loop). But also, I wonder whether this has any unintended and/or surprising consequences.

Related issues:

  • #678 : this is probably why
  • #715 : this PR tries to improve the “autodetection” of async scripts. I start wondering whether we should remove autodetection, declare that all scripts are sync by default and require the user to explicitly opt-in for async semantics

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 21 (21 by maintainers)

Most upvoted comments

@tedpatrick will check this locally on my machine - the link to your branch seems broken (though I can make the same changes myself).

I wanted to add a small wrinkle to the above discussion. In base,ts, the routine “async evaluate()” returns a void promise (but a PROMISE nonetheless). The issue is that in runtime.ts, this is called DIRECTLY from the loop and awaited there. Let us suppose there are good reasons to AWAIT the async routine evaluate() in order to do something after that. How should we do this? The correct solution is to define an async routine, say “HandleScriptEvaluate(script)”, that internally calls the async routine evaluate() and awaits its completion (in order to do something (say log that it is done). This intermediate routine is called from the loop in runtime,ts, which doesnt await anything at all. Doing this is beneficial due to the way await works in Javascript (affects the awaiting routine but not those calling it).

My main point is that we can still usefully await the promise returned by “async evaluate”, but do it one level lower than the loop in a separate routine (thus having our cake and eating it too). Of course, I still have to check that this does work as expected.

One more thing that I will check is whether your solution resolves #678 - in that case there is only a single Python script that loops infinitely, so awaiting it causes later Javascript code that clears messages on the web page to never get reached/executed (if one doesnt await at all then of course the Javascript code executes).