vibe.d-lite v0.1.0 powered by photon

Sönke Ludwig sludwig at outerproduct.org
Mon Sep 22 11:14:17 UTC 2025


Am 22.09.25 um 09:49 schrieb Dmitry Olshansky:
> On Friday, 19 September 2025 at 17:37:36 UTC, Sönke Ludwig wrote:
>> So you don't support timeouts when waiting for an event at all? 
>> Otherwise I don't see why a separate API would be required, this 
>> should be implementable with plain Posix APIs within vibe-core-lite 
>> itself.
> 
> Photon's API is the syscall interface. So to wait on an event you just 
> call poll.
> Behind the scenes it will just wait on the right fd to change state.
> 
> Now vibe-core-light wants something like read(buffer, timeout) which is 
> not syscall API but maybe added. But since I'm going to add new API I'd 
> rather have something consistent and sane not just a bunch of adhoc 
> functions to satisfy vibe.d interface.

Why can't you then use poll() to for example implement `ManualEvent` 
with timeout and interrupt support? And shouldn't recv() with timeout be 
implementable the same way, poll with timeout and only read when ready?

>>>> Telling in what way?
>>>
>>> That running single threaded is the intended model.
>>
>> Obviously this is wrong, though.
> 
> All the examples plus your last statement on process per core being better
> makes me conclude that. I don't see how I'm wrong here.
> 
>> (...)
>>
>> ```
>> runWorkerTaskDist({
>>     HTTPServerSettings settings;
>>     settings.options |= HTTPServerOption.reusePort;
>>     listenHTTP(settings);
>> });
>> ```
> 
> Yet this is not the default, and the default is basically single threaded.
> We have different opinions on what the default should be obviously.

I think we have a misunderstanding of what vibe.d is supposed to be. It 
seems like you are only focused on the web/server role, while to me 
vibe-core is a general-purpose I/O and concurrency system with no 
particular specialization in server tasks. With that view, your 
statement to me sounds like "Clearly D is not meant to do 
multi-threading, since main() is only running in a single thread".

Of course, there could be a high-level component on top of vibe-d:web 
that makes some opinionated assumptions on how to structure a web 
application to ensure it is scalable, but that would go against the idea 
of being a toolkit with functional building blocks, as opposed to a 
framework that dictates your application structure.

>>>> Not everything is CPU bound and using threads "just because" doesn't 
>>>> make sense either. This is especially true, because of low level 
>>>> race conditions that require special care. D's shared/immutable 
>>>> helps with that, but that also means that your whole application 
>>>> suddenly needs to use shared/immutable when passing data between tasks.
>>>
>>> I’m dying to know which application not being cpu bound still needs 
>>> to pass data between tasks that are all running on a single thread.
>>
>> Anything client side involving a user interface has plenty of 
>> opportunities for employing secondary tasks or long-running sparsely 
>> updated state logic that are not CPU bound. Most of the time is spent 
>> idle there. Specific computations on the other hand can of course 
>> still be handed off to other threads.
> 
> Latency still going to be better if multiple cores are utilized.
> And I'm still not sure what the example is.

We are comparing fiber switches and working on data with a shared cache 
and no synchronization to synchronizing data access and control flow 
between threads/cores. There is such a broad spectrum of possibilities 
for one of those to be faster than the other that it's just silly to 
make a general statement like that.

The thing is that if you always share data between threads, you have to 
pay for that for every single data access, regardless of whether there 
is actual concurrency going on or not.

If you want a concrete example, take a simple download dialog with a 
progress bar. There is no gain in off-loading anything to a separate 
thread here, since this is fully I/O bound, but it adds quite some 
communication complexity if you do. CPU performance is simply not a 
concern here.

>>>> But TLS variables are always "globals" in the sense that they 
>>>> outlive the scope that accesses them. A modification in one thread 
>>>> would obviously not be visible in another thread, meaning that you 
>>>> may or may not have a semantic connection when you access such a 
>>>> library sequentially from multiple tasks.
>>>>
>>>> And then there are said libraries that are not thread-safe at all, 
>>>> or are bound to the thread where you initialize them. Or handles 
>>>> returned from a library may be bound to the thread that created 
>>>> them. Dealing with all of this just becomes needlessly complicated 
>>>> and error-prone, especially if CPU cycles are not a concern.
>>>
>>> TLS is fine for using not thread safe library - just make sure you 
>>> initialize it for all threads. I do not switch or otherwise play 
>>> dirty tricks with TLS.
>>
>> The problem is that for example you might have a handle that was 
>> created in thread A and is not valid in thread B, or you set a state 
>> in thread A and thread B doesn't see that state. This would mean that 
>> you are limited to a single task for the complete library interaction.
> 
> Or just initialize it lazily in all threads that happen to use it.
> Otherwise, this is basically stick to one thread really.

But then it's a different handle representing a different object - 
that's not the same thing. I'm not just talking about initializing the 
library as a whole. But even if, there are a lot of libraries that don't 
use TLS and are simply not thread-safe at all.

>>>> By robbing the user the control over where a task spawns, you are 
>>>> also forcing synchronization everywhere, which can quickly become 
>>>> more expensive than any benefits you would gain from using multiple 
>>>> threads.
>>>
>>> Either of default kind of rob user of control of where the task 
>>> spawns. Which is sensible a user shouldn’t really care.
>>
>> This doesn't make sense, in the original vibe-core, you can simply 
>> choose between spawning in the same thread or in "any" thread. 
>> `shared`/`immutable` is correctly enforced in the latter case to avoid 
>> unintended data sharing.
> 
> I have go and goOnSameThread. Guess which is the encouraged option.

Does go() enforce proper use of shared/immutable when passing data to 
the scheduled "go routine"?

>>>> Finally, in the case of web applications, in my opinion the better 
>>>> approach for using multiple CPU cores is *usually* by running 
>>>> multiple *processes* in parallel, as opposed to multiple threads 
>>>> within a single process. Of course, every application is different 
>>>> and there is no one-size-fits-all approach.
>>>
>>> There we differ, not only load balancing is simpler within a single 
>>> application but also processes are more expansive. Current D GC 
>>> situation kind of sucks on multithreaded workloads but that is the 
>>> only reason to go multiprocess IMHO.
>>
>> The GC/malloc is the main reason why this is mostly false in practice, 
>> but it extends to any central contention source within the process - 
>> yes, often you can avoid that, but often that takes a lot of extra 
>> work and processes sidestep that issue in the first place.
> 
> As is observable from the look on other languages and runtimes malloc is 
> not the bottleneck it used to be. Our particular version of GC that 
> doesn't have thread caches is a bottleneck.

malloc() will also always be a bottleneck with the right load. Just the 
n times larger amount of virtual address space required may start to 
become an issue for memory heavy applications. But even if ignore that, 
ruling out using the existing GC doesn't sound like a good idea to me. 
And the fact is that, even with relatively mild GC use, a web 
application will not scale properly with many cores.

>> Also, in the usual case where the threads don't have to communicate 
>> with each other (apart from memory allocation synchronization), a 
>> separate process per core isn't any slower - except maybe when hyper- 
>> threading is in play, but whether that helps or hurts performance 
>> always depends on the concrete workload.
> 
> The fact that context switch has to drop all of virtual address spaces 
> does add a bit of overhead. Though to be certain of anything there 
> better be a benchmark.

There is no context switch involved with each process running on its own 
core.

>> Separate process also have the advantage of being more robust and 
>> enabling seamless restarts and updates of the executable. And they 
>> facilitate an application design that lends itself to scaling across 
>> multiple machines.
> 
> Then give me the example code to run multiple vibe.d in parallel 
> processes (should be simillar to runDist) and we can compare approaches. 
> For all I know it could be faster then multi-threaded vibe.d-light. Also 
> honestly if vibe.d's target is multiple processes it should probably 
> start like this by default.

Again, the "default" is a high-level issue and none of vibe-core's 
business. The simplest way to have that work is to use 
`HTTPServerOption.reusePort` and then start as many processes as desired.


More information about the Digitalmars-d-announce mailing list