node-swift icon indicating copy to clipboard operation
node-swift copied to clipboard

Hangs on async functions with Framework APIs

Open dehesa opened this issue 6 months ago • 4 comments

Hi there @kabiroberai,

Here at Raycast, we are using your package to run some experiments for multiplatform code (Windows and macOS). The issue occurs when we try to run some simple CoreLocation async functions in macOS, which produce hangs (i.e. the code just blocks and never pass the targeted line).

I read and implemented the changes suggested by #48 and #49 to run Swift async functions on Swift 6.1. You can easily replicate by calling an exposing an async function and testing it with an expression such as Los Angeles, United States:

internal import CoreLocation

func landmarksFor(_ expression: String) async throws -> [CLPlacemark] {
  return try await geocoder.geocodeAddressString(expression, in: nil, preferredLocale: .current)
}

Any help would be appreciated.

dehesa avatar Jun 18 '25 14:06 dehesa

hey @dehesa! This is related to #4. The general solution is non-trivial and I'll elaborate when I have a bit more time, but to unblock your experimentation in the short term, try something like

#NodeModule {
  Task { @NodeActor in
    while ({ RunLoop.main.run(mode: .default, before: Date() + 0.01) }()) {
      await Task.yield()
    }
  }
  return [/* exports */]
}

Basically, you have to spin the main runloop yourself since libuv doesn't do that. There is a more viable long-term solution (Electron does this well) but will require some work to integrate into non-Electron-based Node (possibly involving patching the source.)

Some relevant resources:

  • https://github.com/TooTallNate/NodObjC/issues/2
  • https://gist.github.com/trevorlinton/5cc934f9264629d4e85c
  • https://github.com/indutny/node-cf

kabiroberai avatar Jun 20 '25 01:06 kabiroberai

Hey @kabiroberai,

Thank you for the notes. I did try, but it didn't work with my experiment with the geocoder. My guess is that the runloop .run function returns right away with false because there was not a timer or input source attached to it.

Interestingly, I tried to attach a regular Timer to it before calling that run(mode:before:) and nothing still happens.

If no input sources or timers are attached to the run loop, this method exits immediately and returns false.

dehesa avatar Jun 23 '25 13:06 dehesa

strange, I tried using CLGeocoder and it worked fine for me when I set up the RunLoop that way. I do have a better solution now with #52 though. Can you try updating to that branch and seeing if it works for you?

You’ll need to add NodeUV as a dependency and then use NodeCFRunLoop.ref() at the start of your #NodeModule like I've done in the test code in that PR.

kabiroberai avatar Jun 25 '25 02:06 kabiroberai

Hi there @kabiroberai,

Here at Raycast, we are using your package to run some experiments for multiplatform code (Windows and macOS). The issue occurs when we try to run some simple CoreLocation async functions in macOS, which produce hangs (i.e. the code just blocks and never pass the targeted line).

I read and implemented the changes suggested by #48 and #49 to run Swift async functions on Swift 6.1. You can easily replicate by calling an exposing an async function and testing it with an expression such as Los Angeles, United States:

internal import CoreLocation

func landmarksFor(_ expression: String) async throws -> [CLPlacemark] { return try await geocoder.geocodeAddressString(expression, in: nil, preferredLocale: .current) } Any help would be appreciated.

Hi, im also doing some experiments for multi-platform, I just wonder what's your solution for windows platform now?

(sorry this is maybe out of context)

himself65 avatar Jul 06 '25 01:07 himself65