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

Inconsistent behavior with using DispatchQueue on Node vs Electron #4

Open
KishanBagaria opened this issue Mar 13, 2022 · 1 comment

Comments

@KishanBagaria
Copy link
Collaborator

KishanBagaria commented Mar 13, 2022

import NodeAPI
import Foundation

final class AwesomeClass: NodeClass {
  static let properties: NodeClassPropertyList = [
    "foo": NodeMethod(foo),
  ]
  private static let queue = DispatchQueue(label: "new-queue")
  private let swiftJSQueue: NodeAsyncQueue
  
  init(_ args: NodeFunction.Arguments) throws {
    self.swiftJSQueue = try NodeAsyncQueue(label: "new-async")
  }

  private static func returnAsync(
      on jsQueue: NodeAsyncQueue,
      _ action: @escaping () throws -> NodeValueConvertible
  ) throws -> NodePromise {
      try NodePromise { deferred in
          queue.async {
              let result = Result { try action() }
              try? jsQueue.async {
                  try deferred(result)
              }
          }
      }
  }
  private func returnAsync(
      _ action: @escaping () throws -> NodeValueConvertible
  ) throws -> NodePromise {
      try Self.returnAsync(on: swiftJSQueue, action)
  }
  private func performAsync(
      _ action: @escaping () throws -> Void
  ) throws -> NodePromise {
      try returnAsync {
          try action()
          return NodeUndefined.deferred
      }
  }

  func foo(bar: String, baz: String) throws -> NodeValueConvertible {
    try performAsync { 
      print("\(bar) \(baz)")
      DispatchQueue.main.sync {
        print("inside queue: \(bar) \(baz)")
      }
      print("outside queue: \(bar) \(baz)")
    }
  }
}

@main struct AwesomeMod: NodeModule {
  let exports: NodeValueConvertible

  init() throws {
    exports = try NodeObject([
      "AwesomeClass": AwesomeClass.constructor(),
    ])
  }
}
const swift = require(`./binaries/${process.platform}-${process.arch}/node.node`)

new swift.AwesomeClass().foo("a", "b").then(() => console.log("foo promise resolved"))
console.log('done')
$ node index.js
a b
done
^C

$ electron index.js
a b
done
inside queue: a b
outside queue: a b
foo promise resolved
^Cnode_modules/electron/dist/Electron.app/Contents/MacOS/Electron exited with signal SIGINT
@kabiroberai
Copy link
Owner

kabiroberai commented Mar 13, 2022

Reduced example:

import NodeAPI
import Foundation

@main struct AwesomeMod: NodeModule {
  let exports: NodeValueConvertible
  init() throws {
    exports = try NodeFunction { _ in
        DispatchQueue(label: "new-queue").async {
          print("before queue")
          DispatchQueue.main.sync {
            print("inside queue")
          }
          print("after queue")
        }
        // RunLoop.main.run()
        return NodeUndefined.deferred
      }
  }
}

This is happening because DispatchQueue.main.sync enqueues the block onto the main NSRunLoop. Vanilla Node doesn't have a running NSRunLoop, whereas Electron is built to explicitly handle this case (see https://youtu.be/OPhb5GoV8Xk). You can sort of work around this by starting the RunLoop at the end of the function, though that causes it to never return. We should look into servicing the main run loop in a vanilla Node environment alongside libuv, as Electron does.

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

No branches or pull requests

2 participants