Closed instilled closed 8 months ago
I'm glad NodeSwift is working well for you! This is something I've been meaning to document more clearly — Node isn't thread-safe, which means certain APIs need to be called on the NodeJS thread. Any code that's invoked "from" Node (NodeModule initializers, NodeFunction bodies, etc) already runs on the Node thread, but a lot of macOS APIs can take you off the Node thread, onto (e.g.) the Main GCD thread instead. You need to use NodeAsyncQueue
to hop back onto the Node thread. The NodeAsyncQueue
itself needs to be created on the Node thread, so a good place to do this would be your NodeClass
initializer.
@NodeClass final class MyNativeApiFacade {
let nodeQueue: NodeAsyncQueue
@NodeConstructor init() throws {
nodeQueue = NodeAsyncQueue(label: "...")
}
...
@objc func foo() {
nodeQueue.async {
// we're back on the Node thread here!
}
}
}
FYI I'd encourage you to use the @NodeClass
macro coupled with @NodeConstructor
and @NodeMethod
instead of manually declaring conformance to NodeClass
; it simplifies things quite a bit. That's another thing I'm hoping to document soon, but happy to field questions in the meanwhile.
Let me know if the above approach works for you — I'll keep this issue open for now.
@kabiroberai Thank you for your quick reply and help. This worked nicely although I had to change two things:
1) NodeAsyncQueue.async
does not exist neither on 1.1.3
nor main
. Instead I used NodeAsyncQueue.run
.
2) nodeQueue = NodeAsyncQueue(label: "...")
leads to call to global actor 'NodeActor'-isolated initializer 'init(label:asyncResource:maxQueueSize:)' in a synchronous nonisolated context
on my M2 with swift version 5.10. I think this is a complier bug as per this SO. This is easily solved by adding @MainActor
macro to init
.
The following compiles & runs fine:
import NodeAPI
@NodeClass final class MyNativeApiFacade {
private let impl: MyNativeApiImpl
private let nodeQueue: NodeAsyncQueue
@NodeActor // <<<<<<<<< to avoid 'call to global actor ...' (2)
@NodeConstructor init(logLevel: String) throws {
impl = try MyNativeApiImpl(logLevel: logLevel)
nodeQueue = try NodeAsyncQueue(label: "electron-main")
}
@NodeMethod func registerApplicationDidSwitchCallback(fn: NodeFunction) throws {
return try impl.registerApplicationDidSwitchCallback({ (appName: String) throws -> Void in
try self.nodeQueue.run { // <<<<<<<<<<< run instead of async (1)
try fn.call([appName])
}
})
}
}
#NodeModule(exports: ["MyNativeApi": MyNativeApiFacade.deferredConstructor])
Indeed using the macros @NodeConstructor et al makes things much nicer.
Feel free to close the issue.
Cheers again for your help.
Hi,
First of all thank you for the great library. I've been using it for a project and it works great so far. I also like how easy it is to integrated into the build pipeline.
The problem I'm encountering is with invoking a callback from within an swift/cocoa observer (non js thread). The errors I get are either
FATAL ERROR: <path-omitted>/node-swift/Sources/NodeAPI/NodeContext.swift:153 Attempted to call a NodeAPI function on a non-JS thread
orFATAL ERROR: <path-omitted>/node-swift/Sources/NodeAPI/NodeActor.swift:51 There is no target NodeAsyncQueue associated with this Task
depending on how I attempt the invocation. I'm running this in Electron 28.1.4.Attempt 1:
Attempt 2)
Note that it works well when tested without an observer:
Can you point me into the right direction on how to solve this? I've looked at the source code, the examples and the tests trying to find a way around the problem but failed to identify a solution - maybe it's because I'm new to swift.
Cheers!