> how can a message be 'lost' from a mailbox without any explicit 'read-message-from-mailbox' call from the actor itself.
It depends on the implementation of actors. If an actor is represented as a thread/fiber then an actor is responsible to call `receive` method from time to time. The only example of such an approach I know in the C++ world is Just::Thread Pro library. But even in that case, a message can be ignored if a user writes the wrong if-then chain (or `switch` statement).
But actors often implemented as an object with callbacks those are called by actor framework at the appropriate time. List of callbacks can differ from state to state.
There are at least two corner cases in real-world usage of actors:
* traceability of an application (or part of the application). For example, you send a message and don't see any reaction to it. Why? Was this message lost (sent nowhere)? Was it ignored by the receiver (or received by a different actor)? Was it received by the right receiver but handled incorrectly? It could be hard to find an answer, especially if there are millions of actors in your app.
* testability of your actors. Writing a unit-test for an actor can be a more complex task than testing some C++ function or class.
I think a good actor framework should provide tools for simplification of such tasks. And I can't image that those topics will sometime be covered by a C++ standard.
> once message delivery is guaranteed to be in-order and lossless, then for the above scenario the issue is 'obviously' on the sender side.
It depends on how the receiver handles incoming messages:
* there could be a scheme where a message is lost if it isn't handled in the current actor's state (or if it isn't deferred explicitly);
* there could be a scheme where a message that is not handled in the current state is deferred automatically (for example, Erlang's selective receive).
The problem I described exists for the first case but isn't actual for the second. However, schemes with selective receive can have their own drawbacks.
> it can be easily solved with timers where 'A' expects to move from state-a to state-b in say 'n' seconds after startup etc.
The main problem is: a developer should understand that problem and should implement that in code. But people make mistakes...
> Message passing definitely doesn't eliminate race conditions or deadlocks.
You mention two very different problems.
1. Data race. It's a problem of mutation of some shared state. This problem is illuminated in the actor's approach by removing the shared state completely. Every actor has its own state and the only actor can change during handling incoming messages.
Even if we don't speak about actors and use message-passing within some other model (like CSP or Pub/Sub) then it hard to imagine how data race can happen in 1-to-1 interaction between entities in your application.
Or such thing as miss of a message. For example, actor A starts in state S1(A) and waits for message M1 to switch to state S2(A) where it will wait for M2. But actor B sends messages to A in reverse order: M2, then M1. If message M2 is not deferred by A, then M2 will be ignored by A in S1(A), then A switches to S2(A) and will wait for M2, but that message won't go to it.
There are at least QP/C++ [2] and SObjectizer [3].
All three frameworks are used in production, especially QP/C++.
Error handling in C++ actor frameworks is a different topic than such handling in Erlang or other safe language. Because if you do something like division by 0 or deference of null pointer in your C++ actor you will have a different result: in the best case the whole your application will crush, not just one actor.
"CSP message-passing fundamentally involves a rendezvous between the processes involved in sending and receiving the message, i.e. the sender cannot transmit a message until the receiver is ready to accept it."
it not that strict in a practice. In dependency of implementation of CSP there can be CSP-channels with limited and unlimited capacity. If channels with limited capacity are used then some kind of rendezvous is necessary only on attempt to write a new message to a full channel. In that case process-writer will be blocked until process-reader read some messages from the channel.
If size-unlimited channels are used then interaction of CSP-processes will be asynchronous (just like in Actor Model).
There is another important difference (but it also depends on implementation details): CSP-processes are always proactive. They can do something even if there is no messages to read and process.
Otherwise actors are reactive (usually). They react only to incoming messages and do nothing if there is no new message in they inboxes.
This especially true for cases when actors are implemented as objects with callbacks (for example it is true for Akka, CAF, SObjectizer, QP/C++, Orleans and so on).
But there can be implementations of Actor Model where an actor has to call some kind `receive` primitive to get an incoming message. For example it is Erlang programming language and Just::Thread Pro Actor Edition library for C++.
> The fact that Beast allows you to go low level if required, is one if its strengths.
Yes. But the key moment is "if required". With Beast you always have to initiate read and write operations. Just look at Vinnie Falco's code: https://github.com/vinniefalco/CppCon2018/blob/293bedbd5204d...
You perform request processing and then you write the response manually. Even if there is no need to do such low-level operations in that task.
> And moreover, C++ is generally used in projects where such control is required.
Not only. For example, Wt framework is used for Web-interface for smart devices and you don't need to have an extreme performance here.
Another case: we created HTTP API for old C++ code, but this API was used inside a big application only (application was divided into several parts, something like microservices approach). And the main problem was not a performance of HTTP-related part, but the big response times from old C++ code (it takes seconds to process a request).
In that case, if you receive M2 before M1 the instance of M2 will be kept in the queue.
But if you have to write something like that:
then you can easily lose a message by a mistake.