You have probably heard about the RAII (resource acquisition is initialization) idiom in C++, which is very useful for ensuring that resources get released even in case of exceptions. In the classic, synchronous, C++ world, here is an example:
{
someobject o = get_an_o();
// At this point, "o" is holding some resource (e.g., an open file)
call_some_function(o);
return;
// "o" is destroyed if call_some_function() throws, or before the return.
// When it does, the file it was holding is automatically closed.
}
Now, let's consider asynchronous code using Seastar. Imagine that get_an_o() takes some time to do its work (e.g., open a disk file), and returns a future<someobject>. Well, you might think that you can just use stackless coroutines like this:
someobject o = co_await get_an_o();
// At this point, "o" is holding some resource (e.g., an open file)
call_some_function(o);
return;
But there's a problem here... Although the someobject constructor could be made asynchronous (by using a factory function, get_an_o()in this example), thesomeobject` destructor is still synchronous... It gets called by C++ when unwinding the stack, and it can't return a future! So if the object's destructor does need to wait (e.g., to flush the file), you can't use RAII.
Using a seastar::thread allows you to still use RAII because the destructor now can wait. This is because in a seastar::thread any code - including a destructor, may use get() on a future. This doesn't return a future from the destructor (which we can't) - instead it just "pauses" the stackful coroutine (switches to other tasks and later returns back to this stack when the future resolves).