Some questions about cocurrency #106
Replies: 37 comments
-
Hi, thanks for using it! For Question 1: You can share the instance between multiple threads, it's thread safe. But you won't able to evaluate concurrently, the instance uses some global states which prevents this, and quickjs itself doesn't seem to be thread-safe as far as I know. For Question 2: The dispatcher passed in the |
Beta Was this translation helpful? Give feedback.
-
Does every |
Beta Was this translation helpful? Give feedback.
-
If I call |
Beta Was this translation helpful? Give feedback.
-
Is it ok to share a If the dispatcher is used to handle |
Beta Was this translation helpful? Give feedback.
-
Yes.
Yes, the latest will be called when calling it from JS. There is no leak but the native data of the old function will remain until the instance is closed.
Yes, it's okay to do that. Concurrent calls should work too even though they can't be executed concurrently, but it does not work now, probably a bug, will fix this.
Yes.
Yes, async function bindings are called directly from |
Beta Was this translation helpful? Give feedback.
-
Thank you for your quick reply.
But I don't mean the val js : QuickJS = TODO()
val lock : Lock = TODO()
GlobalScope.launch{
lock.lock()
js.evaluate("some code here")
lock.unlock()
}
GlobalScope.launch{
lock.lock()
js.evaluate("some other code here")
lock.unlock()
} the two BUT the lock won't help too if the so if the JS code executed by val js : QuickJS = TODO()
val singleThreadDispatcher : CoroutineDispatcher = TODO()
withContext(singleThreadDispatcher){ // <========== same thread
js.evaluate("some code here")
}
withContext(singleThreadDispatcher){ // <========== same thread
js.evaluate("some other code here")
} Is this true? Please correct me if I am wrong. |
Beta Was this translation helpful? Give feedback.
-
By the way, I am really curious: If new Promise((resolve)=> {
/** some IO code here ... **/
resolve()
}).then(v=>{
/** callback code here ... **/
}) Will the |
Beta Was this translation helpful? Give feedback.
-
No problem.
Sorry for misunderstanding the problem. This is much more a quickjs question than a library question, as I know, quickjs does not use thread locals for anything, and neither does this library, so both multithreaded dispatchers and single-threaded dispatchers are okay in this case.
Even if we don't
In this case, its callback will be executed in the |
Beta Was this translation helpful? Give feedback.
-
How can it be? As far as I know, in JavaScript async/await is just a syntax sugar to simplify the traditional Promise.then() code style. Why would its behaviour differ from the Promise.then() code? |
Beta Was this translation helpful? Give feedback.
-
I have no idea how quickjs handle promises internally, but from the aspect of its API, if we In this library, new Promise(async (resolve) => {
await delay(1000); // execution paused here
resolve();
})
.then(() => {
console.log("Done");
}) If we never await the promise created by new Promise(async (resolve) => {
delay(1000);
resolve();
})
.then(() => {
console.log("Done");
}) |
Beta Was this translation helpful? Give feedback.
-
That's wierd, could you please post some links to the relative quickjs api doc or something? I can't find any description about the promise or await or event-loop on QuickJS's website. Sad. |
Beta Was this translation helpful? Give feedback.
-
I also can't find any doc about quickjs' promise API, I haven't read the code but I believe it just follows the JS spec: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#description |
Beta Was this translation helpful? Give feedback.
-
What do you mean by "the JS execution will be paused"? Do you mean the whole js event loop being stopped, or just the code after the await statement being suspended? If the whole js event loop stops, not only this Promise, but other Promise callbacks won't have a chance to run, and that's definitely not the standard behaviour documented on the MDN. |
Beta Was this translation helpful? Give feedback.
-
Yes. Event loop does not come with the quickjs engine, instead we poll jobs manually in a loop. See |
Beta Was this translation helpful? Give feedback.
-
I have check your code, I think they are just ok except here: internal actual fun invokeAsyncFunction(
args: Array<Any?>,
block: suspend (bindingArgs: Array<Any?>) -> Any?,
) {
ensureNotClosed()
val (resolveHandle, rejectHandle) = promiseHandlesFromArgs(args)
val job = coroutineScope.launch {
try {
val result = block(args.sliceArray(2..<args.size))
jsMutex.withLock {
// Call resolve() on JNI side
invokeJsFunction(
context = context,
globals = globals,
handle = resolveHandle,
args = arrayOf(result)
)
}
} catch (e: Throwable) {
jsMutex.withLock {
// Call reject() on JNI side
invokeJsFunction(
context = context,
globals = globals,
handle = rejectHandle,
args = arrayOf(e)
)
}
}
jsMutex.withLock {
while (executePendingJob(context, globals)) {
// The job is completed, see what we can do next
}
}
}
job.invokeOnCompletion {
jobsMutex.withLockSync { asyncJobs -= job }
}
jobsMutex.withLockSync { asyncJobs += job }
} Why bother adding a while loop to private suspend fun evalAndAwait(evalBlock: suspend () -> Any?): Any? {
ensureNotClosed()
evalException = null
loadModules()
jsMutex.withLock { evalBlock() }
awaitAsyncJobs()
val result = jsMutex.withLock { getEvaluateResult(context, globals) }
handleException()
return result
} |
Beta Was this translation helpful? Give feedback.
-
Sorry, I was wrong, that is impossible. BUT, I still have a question: if |
Beta Was this translation helpful? Give feedback.
-
I got it, var isOkay = false
asyncFunction("fail") {
delay(100)
error("Failed")
}
asyncFunction("okay") {
delay(500)
isOkay = true
}
evaluate<Any?>("okay(); fail();")
assertFalse(isOkay) In In suspend fun awaitAsyncJobs() {
...
jobs.forEach {
it.invokeOnCompletion {
while (executePendingJob(context, globals)) {}
}
}
}
jobs.joinAll() // Evil is here, we can't use the first executePendingJob to fail fast
...
} |
Beta Was this translation helpful? Give feedback.
-
OK, I got it, without BUT now new question comes: If we call |
Beta Was this translation helpful? Give feedback.
-
I think we can remove the private suspend fun awaitAsyncJobs() {
/**
* This is our simple 'event loop'.
*/
while (true) {
val jobs = jobsMutex.withLock { asyncJobs.filter { it.isActive } }
if (jobs.isEmpty()) {
// No jobs are running, no jobs will be added
break
}
jobs.joinAll()
// old jobs finish, new jobs may be introduced by the old jobs
}
} the initial jobs are introduced by |
Beta Was this translation helpful? Give feedback.
-
Yes, this might look good but async function call() {}
await call();
console.log("Done");
await new Promise((resolve) => {
resolve();
});
console.log("Done"); We can probably move it out of the |
Beta Was this translation helpful? Give feedback.
-
Then if the user |
Beta Was this translation helpful? Give feedback.
-
Even if we use So |
Beta Was this translation helpful? Give feedback.
-
It's necessary and it will help. See quickjs-libc's loop: quickjs-libc.c I will also push some tests later. |
Beta Was this translation helpful? Give feedback.
-
If user doesn't use kotlin |
Beta Was this translation helpful? Give feedback.
-
Well, we don't need to wait for them, |
Beta Was this translation helpful? Give feedback.
-
private suspend fun awaitAsyncJobs() {
jsMutex.withLock {
do {
// Execute JS Promises, putting this in while(true) is unnecessary
// since we have the same loop after every asyncFunction call
val execResult = executePendingJob(runtime)
if (execResult is ExecuteJobResult.Failure) {
throw execResult.error
}
} while (execResult == ExecuteJobResult.Success)
}
while (true) {
val jobs = jobsMutex.withLock { asyncJobs.filter { it.isActive } }
if (jobs.isEmpty()) {
// No jobs to run
break
}
jobs.joinAll()
}
} consider JS code like this: new Promise((rs,re)=>{setTimeout(()=>rs(),10_000)}).then((v)=>console.log("timeout reached")) There is no kotlin side What will If |
Beta Was this translation helpful? Give feedback.
-
I don't think we can implement |
Beta Was this translation helpful? Give feedback.
-
OK, I got it. The |
Beta Was this translation helpful? Give feedback.
-
Yes, that's right. Once we enter the loop in |
Beta Was this translation helpful? Give feedback.
-
Converting to discussion. |
Beta Was this translation helpful? Give feedback.
-
I love this library, but I have some questions.
Question1: Can I use exactly one same
QuickJS
instance on two coroutines/threads?Question2: According to the README,
QuickJs.create(Dispatchers.Default)
, what exactly is the dispatcher parameter used for? Must it be a single-thread dispatcher(because JavaScript is a single-thread language)?Beta Was this translation helpful? Give feedback.
All reactions