title: Shutting down Orleans

This document explains how to gracefully shutdown an Orleans silo before application exit, first as a Console app, and then as a Docker container app.

Graceful shutdown - Console app

The following code shows how to gracefully shutdown an Orleans silo console app in response to the user pressing Ctrl+C, which generates the Console.CancelkeyPress event.

Normally when that event handler returns, the application will exit immediately, causing a catastrophic Orleans silo crash and loss of in-memory state. But in the sample code below, we set a.Cancel = true; to prevent the application closing before the Orleans silo has completed its graceful shutdown.

  1. using Microsoft.Extensions.Logging;
  2. using Orleans.Configuration;
  3. using Orleans.Hosting;
  4. using System;
  5. using System.Net;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. namespace MySiloHost {
  9. class Program {
  10. static readonly ManualResetEvent _siloStopped = new ManualResetEvent(false);
  11. static ISiloHost silo;
  12. static bool siloStopping = false;
  13. static readonly object syncLock = new object();
  14. static void Main(string[] args) {
  15. SetupApplicationShutdown();
  16. silo = CreateSilo();
  17. silo.StartAsync().Wait();
  18. /// Wait for the silo to completely shutdown before exiting.
  19. _siloStopped.WaitOne();
  20. }
  21. static void SetupApplicationShutdown() {
  22. /// Capture the user pressing Ctrl+C
  23. Console.CancelKeyPress += (s, a) => {
  24. /// Prevent the application from crashing ungracefully.
  25. a.Cancel = true;
  26. /// Don't allow the following code to repeat if the user presses Ctrl+C repeatedly.
  27. lock (syncLock) {
  28. if (!siloStopping) {
  29. siloStopping = true;
  30. Task.Run(StopSilo).Ignore();
  31. }
  32. }
  33. /// Event handler execution exits immediately, leaving the silo shutdown running on a background thread,
  34. /// but the app doesn't crash because a.Cancel has been set = true
  35. };
  36. }
  37. static ISiloHost CreateSilo() {
  38. return new SiloHostBuilder()
  39. .Configure(options => options.ClusterId = "MyTestCluster")
  40. .UseDevelopmentClustering(options => options.PrimarySiloEndpoint = new IPEndPoint(IPAddress.Loopback, 11111))
  41. .ConfigureLogging(b => b.SetMinimumLevel(LogLevel.Debug).AddConsole())
  42. .Build();
  43. }
  44. static async Task StopSilo() {
  45. await silo.StopAsync();
  46. _siloStopped.Set();
  47. }
  48. }
  49. }

Of course, there are many other ways of achieving the same goal. Below is shown a way, popular online, and misleading, that DOES NOT work properly. It does not work because it sets up a race condition between two methods trying to exit first: the Console.CancelKeyPress event handler method, and the static void Main(string[] args) method. When the event handler method finishes first, which happens at least half the time, the application will hang instead of exiting smoothly.

  1. class Program {
  2. static readonly ManualResetEvent _siloStopped = new ManualResetEvent(false);
  3. static ISiloHost silo;
  4. static bool siloStopping = false;
  5. static readonly object syncLock = new object();
  6. static void Main(string[] args) {
  7. Console.CancelKeyPress += (s, a) => {
  8. Task.Run(StopSilo);
  9. /// Wait for the silo to completely shutdown before exiting.
  10. _siloStopped.WaitOne();
  11. /// Now race to finish ... who will finish first?
  12. /// If I finish first, the application will hang! :(
  13. };
  14. silo = CreateSilo();
  15. silo.StartAsync().Wait();
  16. /// Wait for the silo to completely shutdown before exiting.
  17. _siloStopped.WaitOne();
  18. /// Now race to finish ... who will finish first?
  19. }
  20. static async Task StopSilo() {
  21. await silo.StopAsync();
  22. _siloStopped.Set();
  23. }
  24. }

Graceful shutdown - Docker app

To be completed.