|
|
Subscribe / Log in / New account

Unix sockets

Unix sockets

Posted Jan 1, 2025 19:59 UTC (Wed) by ibukanov (subscriber, #3942)
In reply to: Unix sockets by khim
Parent article: Systemd improves image features and adds varlink API

> Why should switch to 16KiB pages make it larger? It would just use one page instead of four…

The user space stack will grow from 4K to 16K.

> Again: why should context switching have become more expensive? It's more-or-less constant for last ~20 years.

Protection against SPECTRe and other hardware bugs is expensive.

> The fact that languages that actually care about every last bit of performance resisted async/await as much as they could

If async/await existed 30 years ago in C it would make programming in C GUI frameworks so much easier and allow to make GUI apps that do not freeze when writing to slow disks on single-core processors of that age without using threads or mutexes.

Which is another advantage of async/await model. It allows to mix events from different sources without much complexity. For example, consider the problem of adding a timeout to code that is doing a blocking io operation in traditional C++ code. One is forced to use a second thread that just calls sleep(), then sets an atomic flag and the IO thread then checks for the cancel after the IO operation. Alternatively instead of a flag one can try to close a file descriptor to force the IO operation to return. But doing that properly is hard and requires a non-trivial synchronization on the file descriptor access.

Yet await implementation allows to await for results of several IO operations with simpler and more efficient code. Heck, even in plain C on Linux I would rather replace the IO operation with non-blocking one and do a poll loop with the timeout than using threads if I have a choice. Then I just close the file descriptor with no need to worry about any synchronization.

Even Golang is plagued by that problem of having to create extra threads when the code needs to react to a new event. Of cause the threads are green and their creation is cheap and ergonomic, but that still brings a lot of complexity. Golang solution of cause was to add the color in the form of Context argument and gradually change libraries to respect that Context cancellation. They are far from there as file/pipe IO still has no support for that. Plus cancel is rather limited and still does not allow to mix arbitrarily events.

As I understand on Google servers there is simply no need to react to extra events. If necessary the OS just kills the whole process or starts a new one.

So async/await is just simply more universal and performant when implemented right like in Rust even if the code can be less straightforward than would be in cases when Fibers are used at Google.


to post comments

Unix sockets

Posted Jan 1, 2025 21:08 UTC (Wed) by khim (subscriber, #9252) [Link] (7 responses)

> The user space stack will grow from 4K to 16K.

Usespace stack can always be swapped out. Kernel threads are limiting factor, not userspace.

> If async/await existed 30 years ago in C it would make programming in C GUI frameworks so much easier

30 years ago every single platform that was embracing GUI was already implementing async/await. Only await was called PostMessage back then. It worked, but poorly.

> and allow to make GUI apps that do not freeze when writing to slow disks on single-core processors of that age without using threads or mutexes

Nope. To make GUI apps that don't freeze you don't need async/await and, in fact, adding async/await doesn't help.

What you need are preemptively scheduled threads. And they were embraced as soon as thay became feasible.

Except for one platform: Web. JavaScript and DOM were designed as fundamentally single-threaded thing and thus couldn't benefit from the preemptively scheduled threads. And on Windows these threads were so heavy they even tried to add green threads back on the OS level in WindowsNT 3.51SP3.

And in these circumstances, on platforms where threads were too heavy to even contemplate “mullions of threads” approach async/await was added to F#, C#, Haskell, Python, TypeScript, JavaScript (in that order), and only after all that – to C++ and Rust.

Please don't try to play “this was done for the efficiency” tune: this doesn't sound even remotely plausible when languages that should care about efficiency most added async/await last.

No, that was a kludge for inefficient runtimes. And then it was brought to C++ and Rust “from above”… but no one ever compared it to alternatives. Except Google – but since Google embraced “millions of threads” approach decades before async/await was added to C++ it's biased in the other direction.

> But doing that properly is hard and requires a non-trivial synchronization on the file descriptor access.

And doing that with async/await is not easier and requires the exact same same dance. Read about how CancellationTokens work in C#.

You can do the exact same thing with Google Fibers and even normal threads.

The only platform where such cancellation is “easy” (for some definition of “easy”) is Rust – and that's because when heavily pressured to embrace async/await paradigm Rust adopted it in a radically different fashion from all other languages.

But that was just a side effect from how was implemented (Rust doesn't have a runtime and was unwilling to readd it back just for async/await) - and many still think it was a mistake: in practice people don't understand that any async function in Rust can be stopped and then dropped at any await point and there are attempts to fix that problem!

IOW: what you portray as “shining advantage” of async/await is only available in one implementation that arrived late and is considered a problem there by many developers!

> Yet await implementation allows to await for results of several IO operations with simpler and more efficient code.

Why does it even matter? Yes, something that “millions of threads” approach doesn't even need can be easier and faster with async/await… so what?

You have created serious problems for yourself and then heroically managed to overcome them… congrats… but isn't it yet another example of how we build kludges on top of kludges?

> Golang solution of cause was to add the color in the form of Context argument and gradually change libraries to respect that Context cancellation.

And that's what every other language does, too. Even Rust. Only C# starts with non-cancellable green threads and Rust with infinitely cancellable async/await but they both realize that you need to bake cancellation into context and only cancel in places where it's safe to cancel.

Thread-based word also have it's own solution, BTW. And it doesn't work for the exact same reason.

Arbitrary cancellation in arbitrary place is just not safe!

> So async/await is just simply more universal and performant when implemented right like in Rust even if the code can be less straightforward than would be in cases when Fibers are used at Google.

Maybe, this remains to be seen. But that's not even the point that we are discussing here. If you would say that design of async/await that Rust specifically adopted in year 2019 (and that was designed in years 2016-2018, you can find the references here) influenced the decisions of Microsoft in year 2007 (when F# got async) or even in year 2012 (when C# got it in version 5.0) then would recommend you to study how casualty works: “after does not mean because”, sure… but “before” does means “not because”!

It would be interesting to see what Rust would manage to do with kludges that it embraced in async/await, it's really a very exciting work… only it's brought to life by the set of kludges that Rust had when it embraced async/await and not by the fact that Google Fibers are inefficient.

And Rust had to adapt async/await not because they wanted to build something new and unique, nope, Rust embraced async/await because that was popular paradigm in other languages, most of them either not very efficient or highly inefficient languages – and developers wanted the same paradigm in Rust.

Nowhere in that journey an attempt to solve concurrency problems in a different fashion, without “green threads”, was considered – and even Rust (that did an interesting experiment by embracing async/await while trying to reject the “green threads”) got all that it got not because “millions of threads” approach was not considered efficient enough. Also, in practice, only embedded world tried to embrace async/await without “green threads” in Rust.

Even more exciting experiment, but very much result of the set of kludges and design decisions Rust had when it tried to adopt async/await and not result of someone's decision to go redesign the foundations.

Unix sockets

Posted Jan 2, 2025 10:13 UTC (Thu) by smurf (subscriber, #17840) [Link] (1 responses)

> The only platform where such cancellation is “easy” (for some definition of “easy”) is Rust

Another platform where cancellation Just Works is Python. I've been using the Trio async runtime for ages now (or what feels like them, given that the library is ~7 years old), and there's even a way to side-step the two-color problem if necessary ("greenback").

> Thread-based word also have it's own solution

Umm, might you be persuaded to not mix up "its" and "it's"? Thanks.

> Arbitrary cancellation in arbitrary place is just not safe!

which is one reason why the two-color problem sometimes is not a problem and async/await is actually helpful: at every place you can possibly get cancelled (or rescheduled) there's a glaringly obvious "async" or "await" keyword.

Unix sockets

Posted Jan 2, 2025 12:38 UTC (Thu) by Wol (subscriber, #4433) [Link]

> > Thread-based word also have it's own solution

> Umm, might you be persuaded to not mix up "its" and "it's"? Thanks.

Mind you, he's in very good company - the number of NATIVE speakers who mess it up ...

Herewith a Grammar Lesson - the apostrophe should *only* be used to indicate an elision (aka missing letters).

In "ye olde English" (and that's a thorn, not a y), possessives were created by adding "es". In time, people dropped the "e", and the standard ending became " 's ". Except there's a special rule for words that naturally end in "s", for example "James", which may have passed through "James's" before the official standard of " James' " took hold. Mind you, people are happy to use either version.

Now to the thorny question of "its". Here the possessive has always been "its", with no "e". Hence it does NOT have an apostrophe, because there was no letter there to elide. "It's" always is an elision of "it is".

Cheers,
Wol

async/await

Posted Jan 2, 2025 15:10 UTC (Thu) by kleptog (subscriber, #1183) [Link] (4 responses)

> 30 years ago every single platform that was embracing GUI was already implementing async/await. Only await was called PostMessage back then.

No, async/await is a language feature that allows programmers to write asynchronous code in a natural way. I don't see the relevance of PostMessage, it doesn't sleep so is not useful in implementing await. The magic of await is not the triggering of other code, but the fact that the response comes back at the exactly the same point in the calling code.

GUI apps use an event loop, which is not what async/await is about. It may work that way under the hood, the point is *the programmer doesn't need to know or care*. And indeed, Rust apparently does it without event loop (neat trick I may add).

The reason async/await become popular is because it makes the Reactor pattern usable. Creating several new threads for every request is tedious overhead. You may say that 16GB for millions of kernel stacks is cheap, but it's still 16GB you could be doing more useful things with. Most of those threads won't do any syscalls other than memory allocation, so they're wasted space.

(I guess it depends on the system, but in my experience, the I/O related threads form a minority of actual threads.)

I don't know if anyone else here used the Twisted framework before yield/send (the precursor to async/await) were usable. That was definitely the definition of unreadable/hard to reason about code, especially the exception handling. Async/await make sense to anyone with minimal training.

(Interestingly, Twisted got inlineCallbacks also around 2007, right about the time F# got async/await... Something in the air I suppose.)

>Arbitrary cancellation in arbitrary place is just not safe!

Just like arbitrary preemption in arbitrary places is unsafe. Which is why it's helpful if it only happens when you use await. Just like cancelling async tasks is easy, just have await throw a cancellation exception, like Python does.

(I just looked at C# and the cancellation tokens and like, WTF? Why don't they just arrange for the await to throw an exception and unwind the stack the normal way? If the answer is: it was hard to change the runtime to support this they should be shot. Cancellation tokens are an ergonomic nightmare.)

> > Yet await implementation allows to await for results of several IO operations with simpler and more efficient code.

> Why does it even matter? Yes, something that “millions of threads” approach doesn't even need can be easier and faster with async/await… so what?

Umm, you receive a request, which requires doing three other requests in parallel and aggregating the responses. Creating three threads just for that is annoying, but with the right framework you can make it look the same I suppose. But creating a bunch of kernel stacks just so they can spend their lifetime waiting on a socket and exiting seems like overkill.

results = await asyncio.wait([Request1(), Request2(), Request3()], timeout=30)

Is quite readable and does what it says on the tin. And even supports cancellation!

Despite this thread going on for a while, it still appears the only real argument for Google Fibres is "Google uses them internally a lot but no-one else is interested". If there were even one other large business saying they used them and had published sources we'd actually be able to compare results.

async/await

Posted Jan 2, 2025 16:19 UTC (Thu) by khim (subscriber, #9252) [Link] (3 responses)

> I don't see the relevance of PostMessage, it doesn't sleep so is not useful in implementing await.

It doesn't sleep today which means it was useless as await 30 years ago? What kind of logic is that? Besides .await is not about sleeping (in fact it would return right back if there are no other work to do anywhere in the system).

30 years ago is year 1995, that's before Windows 95 and on Win16 PostMessage gives the chance for other GUI window procedures to run and process their GUI changes – and that's the essence of await.

That's how multitasking worked in an era before preemptive multitasking… almost like async/await works today.

And it was a nightmare: GUI wasn't “responsive”, it was incredibly laggy because any app could hog the whole system… just like today may happen with single-tread executors (mostly used in tests).

> The magic of await is not the triggering of other code, but the fact that the response comes back at the exactly the same point in the calling code.

That's, ideed, some kind of magic which can be achieved by the use of some kinds of heavy drugs because it doesn't match the reality. It's just not how async/await works.

Sure there are lots of develops who naïvely believe that's how async/await works, and it may even be the reason for async/await popularity… but it's not how it works.

> GUI apps use an event loop, which is not what async/await is about.

What's the difference? Conceptually “an event loop” does the exact same thing as an executor in async/await world. Sure, most languages hide these details from the developer, but so do applications frameworks (MFC, OWL, etc).

> It may work that way under the hood, the point is *the programmer doesn't need to know or care*.

When programmer doesn't know or care the end result is incredibly laggy and unpleasant mess. Abstractions are leaky and async/await is not an exception.

And if you are Ok with laggy and unpleasant programs then you don't need neither async/await nor “millions of threads”, producing mess is easy with any paradigm used.

> And indeed, Rust apparently does it without event loop (neat trick I may add).

Rust doesn't do it “without event loop”. It just makes it possible to write your own event loop. That was possible to do in Windows 1.0 about 40 years ago, too.

> You may say that 16GB for millions of kernel stacks is cheap, but it's still 16GB you could be doing more useful things with.

So you don't want to lose 5% of memory because you need it… for what exactly?

> I don't know if anyone else here used the Twisted framework before yield/send (the precursor to async/await) were usable.

Ah, right. You need these 5% and CPU cycles of memory saved to waste 95% of computer power on a highly inefficient runtime of a highly inefficient language with GIL. Makes perfect sense. Not.

> (Interestingly, Twisted got inlineCallbacks also around 2007, right about the time F# got async/await... Something in the air I suppose.)

Oh, absolutely. But you just have to understand and accept what problems are solved with these tools.

And these problems are not problems of efficiency or kernel memory waste (after all wasting 16KiB of kernel memory is still less painful than wasting 16MiB of userspace memory).

No, async/await was solving entirely different problem: on OSes and languages that had no efficient threading (or, in some cases no threading at all) it made it possible to write some kind of concurrent processing that actually worked.

That was the real justification, everything was developed in the expected “we would put another layer of kludges on top of pule of kludges that we already have” fashion.

And Google Fibers were developed for that, too… just they were developed for different pole of kludges – but at least they are somewhat relevant to a discussion because they do show that changes in the foundation are possible, even if in limited scope.

> If the answer is: it was hard to change the runtime to support this they should be shot.

Why? They were developing solution for yet another pile of kludges. And invented something that worked best for that particular pile.

> But creating a bunch of kernel stacks just so they can spend their lifetime waiting on a socket and exiting seems like overkill.

But using language that uses 95% of resourtces just for it's own bookkeeping that have nothing to do with the task on hand is not overkill? Get real, please.

> Despite this thread going on for a while, it still appears the only real argument for Google Fibres is "Google uses them internally a lot but no-one else is interested".

And the only counterargument is “we shouldn't waste 5% of resources on Google Fibers after we already wasted 95% of resources on our language runtime”.

Which is even sounding funny. And couldn't be real in any sane world. The real argument (and pretty sensible one) is “Google Fibers do solve the problem they claim to solve but we couldn't use them because of pile of kludges that we have while async/await are more complicated, but they fit into said pile of kludges better”. Because:

> results = await asyncio.wait([Request1(), Request2(), Request3()], timeout=30)

Is quite readable and does what it says on the tin. And even supports cancellation!

Sure, but nothing prevents you from implementing such API with Google Fibers. Except if you have language with heavy threads (like C#) or GIL (like Python).

But if you have committed yourself to such a language then you have already wasted more resources than Google Fibers may ever waste!

It's stupid and pointless to talk about async/await as if they are better in general if the only reason they exist is deficiency in our OSes and language runtimes.

P.S. Again, if someone would have went to the foundations and fixed that, by replacing syscalls with something better then complains about wasteful nature of Google Fibers would have sounded sensible. Google Fibers are not perfect. But the fact remains that all current async/await alternatives are even more wasteful! Except Rust, maybe, but then Rust was very late to the party and Rust developers have also accepted “pile of kludges” reasoning, they just kinda said ”if we need to adopt async/await then we may as well use that opportunity to add something to the language that we wanted to have since day one”.

async/await

Posted Jan 3, 2025 9:54 UTC (Fri) by smurf (subscriber, #17840) [Link] (2 responses)

> > The magic of await is not the triggering of other code, but the fact that the response comes back at the exactly the same point in the calling code.

> That's, ideed, some kind of magic which can be achieved by the use of some kinds of heavy drugs because it doesn't match the reality. It's just not how async/await works.

The point is that this is how async/await looks like to the programmer – just another method call, with an additional keyword.

The fact that there's a stack unwind/rewind or promises (plus closures) or some other deep magic underneath, along with an event loop or an io_uring or whatever, is entirely irrelevant to a high-level programmer, for much the same reason that most high-level languages no longer have a "goto" statement even though that's all the underlying hardware is capable of.

async/await

Posted Jan 3, 2025 13:28 UTC (Fri) by khim (subscriber, #9252) [Link] (1 responses)

> The point is that this is how async/await looks like to the programmer – just another method call, with an additional keyword.

And what's the difference from call to PostMessage that also look like “just a function call”, but have many subtle implications, including possible switch of context to handle other GUI objects?

You couldn't have both ways: either we do care about implementation (and then async/await is just a set of green threads with funky interface) or we don't care about implementation (and then it's just new edition of GetMessage/PostMessage).

> The fact that there's a stack unwind/rewind or promises (plus closures) or some other deep magic underneath, along with an event loop or an io_uring or whatever, is entirely irrelevant to a high-level programmer, for much the same reason that most high-level languages no longer have a "goto" statement even though that's all the underlying hardware is capable of.

Sure, but then we should stop pretending that Google Fibers were rejected because they weren't efficient enough. After wasting 50%+ of memory complaining about 5% is just strange. And compared to overhead of dynamic typing cost of syscalls is laughable, too.

I'm not arguing about “goodness” of the fact that async/await was adopted and Google Fibers ignored. I'm just showing that in both cases choice was made not because it's actually better for something – but because it was better fit for the pile of kludges that there was accumulated already. Or maybe better to say not even “kludges” but horse's assed: in many cases it's not even kludges that dictate our choices but some past decisions that “made sense at the time”. Like use of NUL as string terminator. How many things that “clever” decisions brought? From changes to the languages (like PChar in Pascal/Delphi) and straight to design of our CPUs (e.g. RISC-V Fault-Only-First Load may as well be called “strlen load”). Lots of crazy kludges, but can we reverse that decisions? Very unlikely.

async/await

Posted Jan 3, 2025 20:59 UTC (Fri) by kleptog (subscriber, #1183) [Link]

> And what's the difference from call to PostMessage that also look like “just a function call”,

Well, await returns a value, and PostMessage() return void, so they're apples and oranges really. Sure, there's GetMessage(), but that returns *a* message which is useless from the programmer's perspective. You want the results of the async function you just called, not just any response.

Sure, it's a point at which your code can be preempted to run other code, but in a multithreaded program that's everywhere, so not really a new problem.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds