On Wed, 03 Jul 2013 11:12:17 GMT, Dicebot wrote:

On Wed, 03 Jul 2013 09:49:56 GMT, Sönke Ludwig wrote:

Together with the lock() function in vibe.core.concurrency and Insolated!T it becomes a pretty useful tool to get type-safe, race-free shared memory access. lock() will internally cast away shared in a controlled scope, but only if it is safe to do so (e.g. it can be statically proven that no reference to shared memory escapes the scope).

The recent improvements in the compiler regarding unique(/"isolated") expressions will help making this even more useful, although some current limitations actually have made DMD 2.063 more cumbersome to work with (http://d.puremagic.com/issues/show_bug.cgi?id=10012, this unfortunately broke some valid constructor calls and now requires using cast).

I have no objections against the fact that it will be convenient and type-safe. My point is that any global synchronized access is an enormous performance killer comparing to "all thread-local" scenario when it comes to high concurrency models. Making it default may limit any further options. Not sure if result is worth the change.

I see. However, at least with current hardware, compared with all the other things that happen during a request this will still be a small fraction of the total CPU time (as it is highly unlikely to ever have actual contention for the MemorySessionStore lock, it should be well below 1000 CPU cycles for a single lock/unlock). An alternative idea could be to use thread-local (or even task-local) session instances internally and move them lazily between threads/tasks as needed. This would then only incur an inter-thread dependency whenever there actually are two requests for the same session in different threads (which is less likely due to keep-alive connections).

But apart from this I also suppose that in many large-scale scenarios sessions will be stored in an external database (such as Redis), which is then possibly distributed among multiple servers. Compared to the I/O overhead there, a simple atomic CAS will always be negligible.

Anyway the API needs to be fixed somehow. My idea was to make Session a struct instead of a class so that at least that part of the API is invariant to shared vs. non-shared vs. moving between threads. It's also one unnecessary allocation less per session. For SessionStore I think there is no way around making it shared, since it is infact shared between threads and that should be documented. If it then uses locks internally or some other means to protect the data is an implementation detail.

Worker threads can be considered independent processing nodes (with the exception of the shared SessionStore). But adjusting the distribution of requests among threads will unfortunately not work, as solely the OS decides which incoming connection is handled by which thread (keep-alive connections will stay on the same thread, though).

Well, keep-alive connections are technically the same and one connection, so no wonders here :) I guess I need to study some docs on UNIX sockets to see if that can be controlled on OS level. What I am trying to do here is to create simple end-user guidelines regarding concurrency architecture that will scale flawlessly for typical simple web apps while still allowing developer to use one of most seductive vibe.d features - great performance with minimal efforts.

Unfortunately, my earlier experience with this type of services is about kernel mode processing where one can have full control about whole network stack and scheduling - still having lot of difficulties connecting that experience with user space environment :(

Yeah, abstractions can sometimes make simple things really obscure... I'm currently having simlar fun with WinRT ;)

But I guess having a thread-safe session object will be good enough in most cases. Keep in mind that even on a single thread it will be possible to have high-level race conditions due to multiple "parallel" fibers running, so forcing the same thread per source would not help much there.

I was quite sure that race conditions can never happen between fibers within one thread as contexts for those get switched only in certain predefined places (usually after registering an event for async I/O operation). Am I wrong?

For low-level (memory/execution model related) race-conditions this is right, but higher level ones are still possible:

req.session["test"] = (req.session["test"].to!int() + 1).to!string;

If executed concurrently in multiple tasks on the same thread, this could drop some increments if the session object for some reason involves a blocking operation (e.g. because it uses a database as the backing store). But never mind... since this wasn't even the concern...