--- layout: post.liquid title: Async Part 5 - How do async functions in Hack behave published_date: 2021-06-09 23:55:00 -0700 --- Hack programs execute on the [HHVM runtime](https://hhvm.com/). In this article we are going to perform a series of experiments on HHVM. We will use these experiments to infer properties about how async works. I often find that is can be faster to write a small test program to figure out how something works than dig through it's documentation or source code. ### Simple Async Function Let's start with something that will help reveal the flow of execution through an async function: ```php { echo "myAsyncFunction: entry\n"; await HH\Asio\usleep(100000); echo "myAsyncFunction: returning\n"; return $a + $b; } <<__EntryPoint>> async function main(): Awaitable { echo "main: enter\n"; $wh = myAsyncFunction(1, 2); echo "main: called async function and got WH, awaiting\n"; $num = await $wh; echo "main: awaited, got: " . $num . "\n"; } ``` This outputs: ``` main: enter myAsyncFunction: entry main: called async function and got WH, awaiting myAsyncFunction: returning main: awaited, got: 3 ``` We can infer from this output that `myAsyncFunction()` starts executing code as soon as we call it, before it returns anything. So async functions will opportunistically try to execute synchronously until they reach an `await`. This increases efficiency, because we don't have to yield to the scheduler unless we actually await. ### Concrete return type of async functions Let's see if we can glean any information from the return type of the objects returned from a few different async functions: ```php { await HH\Asio\usleep(100000); return $a + $b; } async function getInt() : Awaitable { return 42; } <<__EntryPoint>> function main() { echo var_dump(myAsyncFunction(1,2)); echo var_dump(getInt()); echo var_dump(curl_multi_await(curl_multi_init())); } ``` When run, this outputs: ``` object(HH\AsyncFunctionWaitHandle) (0) { } object(HH\StaticWaitHandle) (0) { } object(HH\ExternalThreadEventWaitHandle) (0) { } ``` We can immediately see that there different async function return different types of objects. The type hierarchy of these different subclasses of `Awaitable` is defined in the [hphp/hack/hhi/classes.hhi](https://github.com/facebook/hhvm/blob/7cf24ba70e3b7356264234aac7f3f0812497806e/hphp/hack/hhi/classes.hhi) file in the HHVM source code. Our `myAsyncFunction()` returns `AsyncFunctionWaitHandle`, which seems reasonable. It is an async function. `getInt()` returns `StaticWaitHandle`. This appears to be an optimization for async functions that don't ever await anything. This pairs well with the eager synchronous execution behavior we described earlier. The last one is the most interesting. This the `ExternalThreadEventWaitHandle` is an extensibility point for plugging async operations into the HHVM runtime. If we look at the PHP source code for this function, we find that this function [has no body defined](https://github.com/facebook/hhvm/blob/7cf24ba70e3b7356264234aac7f3f0812497806e/hphp/runtime/ext/curl/ext_curl.php#L299-L301): ```php <<__Native("NoFCallBuiltin")>> function curl_multi_await(resource $mh, float $timeout = 1.0): Awaitable; ``` It is marked with the `__Native` attribute, which means it calls into the C++ runtime. `grep`ing in the C++ source code, we find [this definition](https://github.com/facebook/hhvm/blob/7cf24ba70e3b7356264234aac7f3f0812497806e/hphp/runtime/ext/curl/ext_curl.cpp#L626-L637): ```c++ Object HHVM_FUNCTION(curl_multi_await, const Resource& mh, double timeout /*=1.0*/) { CHECK_MULTI_RESOURCE_THROW(curlm); auto ev = new CurlMultiAwait(curlm, timeout); try { return Object{ev->getWaitHandle()}; } catch (...) { assertx(false); ev->abandon(); throw; } } ``` We see it is creating an new instance of the `CurlMultiAwait` class and returning a wait handle off of it. Looking at the [definition of the `CurlMultiAwait`](https://github.com/facebook/hhvm/blob/master/hphp/runtime/ext/curl/curl-multi-await.h#L12) class, we can see it inherits from `AsioExternalThreadEvent`. This name sounds familiar; it is pretty similar to the `ExternalThreadEventWaitHandle` we saw earlier. Looking at the [definition of `AsioExternalThreadEvent`](https://github.com/facebook/hhvm/blob/7cf24ba70e3b7356264234aac7f3f0812497806e/hphp/runtime/ext/asio/asio-external-thread-event.h) we find a big, juicy doc-comment. It states: > A root class of all classes of objects representing events external to > the web request thread that synchronizes on them using ASIO framework. This appears to be the main extensibility point for adding new types of async functionality to HHVM. It is a mechnism that allow extension authors to connect their thread-based concurrency tools into the single-threaded world of Hack. ### Conclusion With a little experimentation, we were able to find out some interesting properties of how `async`/`await` works in Hack. With a bit more `grep`ing, we were able to find the interface between the world of Hack and the C++ code implementing the async abstractions in the HHVM runtime. With a bit more digging, I think I will be able to paint a clearer picture of how the async systems work in the HHVM runtime.