How Do Callbacks work in Non-blocking Design?
My understanding of non-blocking architectures is that the common approach is to still have some Thread waiting/blocking on the I/O work somewhere
If a thread is getting blocked somewhere, it is not really a non-blocking architecture. So no, that's not really a correct understanding of it. That doesn't mean that this is necessarily bad. Sometimes you just have to deal with blocking (using JDBC, for example). It would be better to push it off into a fixed thread pool designated for blocking, rather than allowing the entire application to suffer thread starvation.
Thread A is now off doing something else and has no association any more with the original request. How does the Result in the Future get sent back to the client socket?
Using Future
s, it really depends on the ExecutionContext
. When you create a Future
, where the work is done depends on the ExecutionContext
.
val f: Future[?] = ???val g: Future[?] = ???
f
and g
are created immediately, and the work is submitted to a task queue in the ExecutionContext
. We cannot guarantee which will actually execute or complete first in most cases. What you do with the values matters is well. Obviously if you use an Await
to wait for the completion of the Future
s, then we block the current thread. If we map
them and do something with the values, then we again need another ExecutionContext
to submit the task to. This gives us a chain of tasks that are asynchronously getting submitted and re-submitted to the executor for execution every time we manipulate the Future
.
Eventually there needs to be some onComplete
at the end of that chain to return the pass along that value to something, whether it's writing to stream, or something else. ie., it is probably out of the hands of the original thread.
Q1: No, at least not at the user code level. Hopefully your async I/O ultimately comes down to an async kernel API (e.g. select()
). Which in turn will be using DMA to do the I/O and trigger an interrupt when it's done. So it's async at least down to the hardware level.
Q2: Thread B completes the Future
. If you're using something like onComplete
, then thread B will trigger that (probably by creating a new task and handing that task off to a thread pool to pick it up later) as part of the completing call. If a different thread has called Await
to block on the Future
, it will trigger that thread to resume. If nothing has accessed the Future
yet, nothing in particular happens - the value sits there in the Future
until something uses it. (See PromiseCompletingRunnable
for the gritty details - it's surprisingly readable).