Edit

Share via


Immediate and delayed confirmations

In this article, you learn the differences between immediate and delayed confirmations.

Immediate confirmation

For many applications, you want to ensure events are confirmed immediately. This prevents the persisted version from lagging behind the current version in memory and avoids the risk of losing the latest state if the grain fails. You can guarantee this by following these rules:

  1. Confirm all RaiseEvent calls using ConfirmEvents before the grain method returns.
  2. Ensure tasks returned by RaiseConditionalEvent complete before the grain method returns.
  3. Avoid ReentrantAttribute or AlwaysInterleaveAttribute attributes, so only one grain call processes at a time.

If you follow these rules, it means that after an event is raised, no other grain code can execute until the event has been written to storage. Therefore, it's impossible to observe inconsistencies between the in-memory version and the stored version. While this is often exactly what you want, it also has some potential disadvantages.

Potential disadvantages

  • If the connection to a remote cluster or storage is temporarily interrupted, the grain becomes unavailable. Effectively, the grain cannot execute any code while stuck waiting to confirm events, which can take an indefinite amount of time (the log-consistency protocol keeps retrying until storage connectivity is restored).

  • When handling many updates to a single grain instance, confirming them one at a time can become very inefficient, potentially causing poor throughput.

Delayed confirmation

To improve availability and throughput in the situations mentioned above, grains can choose to do one or both of the following:

  • Allow grain methods to raise events without waiting for confirmation.
  • Allow reentrancy, so the grain can keep processing new calls even if previous calls get stuck waiting for confirmation.

This means grain code can execute while some events are still being confirmed. The JournaledGrain<TGrainState,TEventBase> API has specific provisions giving you precise control over how to handle unconfirmed events currently in flight.

You can examine the following property to find out which events are currently unconfirmed:

IEnumerable<EventType> UnconfirmedEvents { get; }

Also, since the state returned by the JournaledGrain<TGrainState,TEventBase>.State property doesn't include the effect of unconfirmed events, there's an alternative property:

StateType TentativeState { get; }

This property returns a "tentative" state, obtained from State by applying all unconfirmed events. The tentative state is essentially a "best guess" at what will likely become the next confirmed state after all unconfirmed events are confirmed. However, there's no guarantee it actually will become the confirmed state. This is because the grain might fail, or the events might race against other events and lose, causing them to be canceled (if conditional) or appear later in the sequence than anticipated (if unconditional).

Concurrency guarantees

Note that Orleans turn-based scheduling (cooperative concurrency) guarantees always apply, even when using reentrancy or delayed confirmation. This means that even though several methods might be in progress, only one can be actively executing—all others are stuck at an await. Therefore, there are never any true races caused by parallel threads.

In particular, note that:

These guarantees assume your code stays within the recommended practices concerning tasks and async/await (in particular, doesn't use thread pool tasks, or only uses them for code that doesn't call grain functionality and that are properly awaited).