swiftwasm / swift

WebAssembly support for the Swift programming language
https://swiftwasm.org
1.3k stars 28 forks source link

Seems async `Task` doesn't work #5376

Closed MihaelIsaev closed 1 year ago

MihaelIsaev commented 1 year ago

Description

I'm trying to execute async code wrapped into Task but it never called.

Steps to reproduce

  1. Create new carton project using carton init
  2. Use the following example
@main
public struct CartonApp {
    public private(set) var text = "Hello, World!"

    public static func main() {
        print(CartonApp().text)
        Task {
            print("async Task works") // this never calls
        }
    }
}

Expected behavior

async Task works should be printed into console

Environment

MihaelIsaev commented 1 year ago

I just tried to use JSClosure.async which doesn't work. Code inside of Task inside of synchronous JSClosure doesn't work also. (it is same as JSClosure.async) Then I tried to create new carton project just to check if it works there, but unfortunately it doesn't.

The only place where Task works is

static func main() async {
    Task {
        print("🔆🔆🔆🔆🔆🔆") // works
    }
}

But it never works anywhere later.

And btw it throws this in the WebInspector console

Screenshot 2023-04-08 at 19 53 04

Not sure if I'm missing something. @kateinoigakukun please help 🙏

MihaelIsaev commented 1 year ago

One more example

import JavaScriptKit

@main
public struct CartonApp {
    public private(set) var text = "Hello, World!"

    public static func main() async {
        print(CartonApp().text)
        if let docObj = JSObject.global.document.object, let bodyObj = JSObject.global.document.jsValue.body.object {
            var button = JSObject.global.document.createElement.function?.callAsFunction(this: docObj, "button").jsValue
            button?.innerText = "Click me".jsValue
            JSObject.global.document.jsValue.body.appendChild.function?.callAsFunction(this: bodyObj, button)
            if let buttonObj = button?.object {
                button?.addEventListener.function?.callAsFunction(this: buttonObj, "click", JSClosure({ args in
                    print("clicked") // works
                    Task {
                        print("clicked inside of Task") // never called
                    }
                    return .undefined
                }))
            }
        }
    }
}

JSClosure.async doesn't work at all

button?.addEventListener.function?.callAsFunction(this: buttonObj, "click", JSClosure.async({ args in
    print("clicked async") // never called
    return .undefined
}))
yonihemi commented 1 year ago

Thanks for reporting. This code however won't run as you expect, even on macOS:

@main
public struct CartonApp {
    public private(set) var text = "Hello, World!"

    public static func main() {
        print(CartonApp().text)
        Task {
            print("async Task works") // this never calls
        }
    }
}

Expected behavior

async Task works should be printed into console

It's a misunderstanding of the main function's lifetime. Once you start the task, the synchronous context reaches its end and there's nothing telling it to keep alive. On other platform you can use CFRunLoopRun() to signal 'keep my process alive', but that's not supported in SwiftWasm. The correct way to achieve that for the main function would be changing the signature from public static func main() to public static func main() async, remove the task and directly use async code. This is supported in latest SwiftWasm toolchains.

MihaelIsaev commented 1 year ago

@yonihemi Thank you for the reply. Could you please help with JSClosure.async ? It doesn't work.

import JavaScriptKit

@main
public struct CartonApp {
    public private(set) var text = "Hello, World!"

    public static func main() async {
        print(CartonApp().text)
        if let docObj = JSObject.global.document.object, let bodyObj = JSObject.global.document.jsValue.body.object {
            var button = JSObject.global.document.createElement.function?.callAsFunction(this: docObj, "button").jsValue
            button?.innerText = "Click me".jsValue
            JSObject.global.document.jsValue.body.appendChild.function?.callAsFunction(this: bodyObj, button)
            if let buttonObj = button?.object {
                button?.addEventListener.function?.callAsFunction(this: buttonObj, "click", JSClosure.async({ args in
                    print("clicked async") // never called
                    return .undefined
                }))
            }
        }
    }
}
MaxDesiatov commented 1 year ago

I don't see calls to JavaScriptEventLoop.installGlobalExecutor() in your sample code, per JSKit documentation in its README.md.

MihaelIsaev commented 1 year ago

That's awesome, that's exactly what I missed!🔥 Thank you very much for pointing me @MaxDesiatov I appreciate it!