GrpcEnvironment.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. #region Copyright notice and license
  2. // Copyright 2015 gRPC authors.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. #endregion
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Linq;
  19. using System.Runtime.InteropServices;
  20. using System.Threading.Tasks;
  21. using Grpc.Core.Internal;
  22. using Grpc.Core.Logging;
  23. using Grpc.Core.Utils;
  24. namespace Grpc.Core
  25. {
  26. /// <summary>
  27. /// Encapsulates initialization and shutdown of gRPC library.
  28. /// </summary>
  29. public class GrpcEnvironment
  30. {
  31. const int MinDefaultThreadPoolSize = 4;
  32. const int DefaultBatchContextPoolSharedCapacity = 10000;
  33. const int DefaultBatchContextPoolThreadLocalCapacity = 64;
  34. static object staticLock = new object();
  35. static GrpcEnvironment instance;
  36. static int refCount;
  37. static int? customThreadPoolSize;
  38. static int? customCompletionQueueCount;
  39. static bool inlineHandlers;
  40. static int batchContextPoolSharedCapacity = DefaultBatchContextPoolSharedCapacity;
  41. static int batchContextPoolThreadLocalCapacity = DefaultBatchContextPoolThreadLocalCapacity;
  42. static readonly HashSet<Channel> registeredChannels = new HashSet<Channel>();
  43. static readonly HashSet<Server> registeredServers = new HashSet<Server>();
  44. static ILogger logger = new LogLevelFilterLogger(new ConsoleLogger(), LogLevel.Off, true);
  45. readonly IObjectPool<BatchContextSafeHandle> batchContextPool;
  46. readonly GrpcThreadPool threadPool;
  47. readonly DebugStats debugStats = new DebugStats();
  48. readonly AtomicCounter cqPickerCounter = new AtomicCounter();
  49. bool isShutdown;
  50. /// <summary>
  51. /// Returns a reference-counted instance of initialized gRPC environment.
  52. /// Subsequent invocations return the same instance unless reference count has dropped to zero previously.
  53. /// </summary>
  54. internal static GrpcEnvironment AddRef()
  55. {
  56. ShutdownHooks.Register();
  57. lock (staticLock)
  58. {
  59. refCount++;
  60. if (instance == null)
  61. {
  62. instance = new GrpcEnvironment();
  63. }
  64. return instance;
  65. }
  66. }
  67. /// <summary>
  68. /// Decrements the reference count for currently active environment and asynchronously shuts down the gRPC environment if reference count drops to zero.
  69. /// </summary>
  70. internal static async Task ReleaseAsync()
  71. {
  72. GrpcEnvironment instanceToShutdown = null;
  73. lock (staticLock)
  74. {
  75. GrpcPreconditions.CheckState(refCount > 0);
  76. refCount--;
  77. if (refCount == 0)
  78. {
  79. instanceToShutdown = instance;
  80. instance = null;
  81. }
  82. }
  83. if (instanceToShutdown != null)
  84. {
  85. await instanceToShutdown.ShutdownAsync().ConfigureAwait(false);
  86. }
  87. }
  88. internal static int GetRefCount()
  89. {
  90. lock (staticLock)
  91. {
  92. return refCount;
  93. }
  94. }
  95. internal static void RegisterChannel(Channel channel)
  96. {
  97. lock (staticLock)
  98. {
  99. GrpcPreconditions.CheckNotNull(channel);
  100. registeredChannels.Add(channel);
  101. }
  102. }
  103. internal static void UnregisterChannel(Channel channel)
  104. {
  105. lock (staticLock)
  106. {
  107. GrpcPreconditions.CheckNotNull(channel);
  108. GrpcPreconditions.CheckArgument(registeredChannels.Remove(channel), "Channel not found in the registered channels set.");
  109. }
  110. }
  111. internal static void RegisterServer(Server server)
  112. {
  113. lock (staticLock)
  114. {
  115. GrpcPreconditions.CheckNotNull(server);
  116. registeredServers.Add(server);
  117. }
  118. }
  119. internal static void UnregisterServer(Server server)
  120. {
  121. lock (staticLock)
  122. {
  123. GrpcPreconditions.CheckNotNull(server);
  124. GrpcPreconditions.CheckArgument(registeredServers.Remove(server), "Server not found in the registered servers set.");
  125. }
  126. }
  127. /// <summary>
  128. /// Requests shutdown of all channels created by the current process.
  129. /// </summary>
  130. public static Task ShutdownChannelsAsync()
  131. {
  132. HashSet<Channel> snapshot = null;
  133. lock (staticLock)
  134. {
  135. snapshot = new HashSet<Channel>(registeredChannels);
  136. }
  137. return Task.WhenAll(snapshot.Select((channel) => channel.ShutdownAsync()));
  138. }
  139. /// <summary>
  140. /// Requests immediate shutdown of all servers created by the current process.
  141. /// </summary>
  142. public static Task KillServersAsync()
  143. {
  144. HashSet<Server> snapshot = null;
  145. lock (staticLock)
  146. {
  147. snapshot = new HashSet<Server>(registeredServers);
  148. }
  149. return Task.WhenAll(snapshot.Select((server) => server.KillAsync()));
  150. }
  151. /// <summary>
  152. /// Gets application-wide logger used by gRPC.
  153. /// </summary>
  154. /// <value>The logger.</value>
  155. public static ILogger Logger
  156. {
  157. get
  158. {
  159. return logger;
  160. }
  161. }
  162. /// <summary>
  163. /// Sets the application-wide logger that should be used by gRPC.
  164. /// </summary>
  165. public static void SetLogger(ILogger customLogger)
  166. {
  167. GrpcPreconditions.CheckNotNull(customLogger, "customLogger");
  168. logger = customLogger;
  169. }
  170. /// <summary>
  171. /// Sets the number of threads in the gRPC thread pool that polls for internal RPC events.
  172. /// Can be only invoked before the <c>GrpcEnviroment</c> is started and cannot be changed afterwards.
  173. /// Setting thread pool size is an advanced setting and you should only use it if you know what you are doing.
  174. /// Most users should rely on the default value provided by gRPC library.
  175. /// Note: this method is part of an experimental API that can change or be removed without any prior notice.
  176. /// </summary>
  177. public static void SetThreadPoolSize(int threadCount)
  178. {
  179. lock (staticLock)
  180. {
  181. GrpcPreconditions.CheckState(instance == null, "Can only be set before GrpcEnvironment is initialized");
  182. GrpcPreconditions.CheckArgument(threadCount > 0, "threadCount needs to be a positive number");
  183. customThreadPoolSize = threadCount;
  184. }
  185. }
  186. /// <summary>
  187. /// Sets the number of completion queues in the gRPC thread pool that polls for internal RPC events.
  188. /// Can be only invoked before the <c>GrpcEnviroment</c> is started and cannot be changed afterwards.
  189. /// Setting the number of completions queues is an advanced setting and you should only use it if you know what you are doing.
  190. /// Most users should rely on the default value provided by gRPC library.
  191. /// Note: this method is part of an experimental API that can change or be removed without any prior notice.
  192. /// </summary>
  193. public static void SetCompletionQueueCount(int completionQueueCount)
  194. {
  195. lock (staticLock)
  196. {
  197. GrpcPreconditions.CheckState(instance == null, "Can only be set before GrpcEnvironment is initialized");
  198. GrpcPreconditions.CheckArgument(completionQueueCount > 0, "threadCount needs to be a positive number");
  199. customCompletionQueueCount = completionQueueCount;
  200. }
  201. }
  202. /// <summary>
  203. /// By default, gRPC's internal event handlers get offloaded to .NET default thread pool thread (<c>inlineHandlers=false</c>).
  204. /// Setting <c>inlineHandlers</c> to <c>true</c> will allow scheduling the event handlers directly to
  205. /// <c>GrpcThreadPool</c> internal threads. That can lead to significant performance gains in some situations,
  206. /// but requires user to never block in async code (incorrectly written code can easily lead to deadlocks).
  207. /// Inlining handlers is an advanced setting and you should only use it if you know what you are doing.
  208. /// Most users should rely on the default value provided by gRPC library.
  209. /// Note: this method is part of an experimental API that can change or be removed without any prior notice.
  210. /// Note: <c>inlineHandlers=true</c> was the default in gRPC C# v1.4.x and earlier.
  211. /// </summary>
  212. public static void SetHandlerInlining(bool inlineHandlers)
  213. {
  214. lock (staticLock)
  215. {
  216. GrpcPreconditions.CheckState(instance == null, "Can only be set before GrpcEnvironment is initialized");
  217. GrpcEnvironment.inlineHandlers = inlineHandlers;
  218. }
  219. }
  220. /// <summary>
  221. /// Sets the parameters for a pool that caches batch context instances. Reusing batch context instances
  222. /// instead of creating a new one for every C core operation helps reducing the GC pressure.
  223. /// Can be only invoked before the <c>GrpcEnviroment</c> is started and cannot be changed afterwards.
  224. /// This is an advanced setting and you should only use it if you know what you are doing.
  225. /// Most users should rely on the default value provided by gRPC library.
  226. /// Note: this method is part of an experimental API that can change or be removed without any prior notice.
  227. /// </summary>
  228. public static void SetBatchContextPoolParams(int sharedCapacity, int threadLocalCapacity)
  229. {
  230. lock (staticLock)
  231. {
  232. GrpcPreconditions.CheckState(instance == null, "Can only be set before GrpcEnvironment is initialized");
  233. GrpcPreconditions.CheckArgument(sharedCapacity >= 0, "Shared capacity needs to be a non-negative number");
  234. GrpcPreconditions.CheckArgument(threadLocalCapacity >= 0, "Thread local capacity needs to be a non-negative number");
  235. batchContextPoolSharedCapacity = sharedCapacity;
  236. batchContextPoolThreadLocalCapacity = threadLocalCapacity;
  237. }
  238. }
  239. /// <summary>
  240. /// Occurs when <c>GrpcEnvironment</c> is about the start the shutdown logic.
  241. /// If <c>GrpcEnvironment</c> is later initialized and shutdown, the event will be fired again (unless unregistered first).
  242. /// </summary>
  243. public static event EventHandler ShuttingDown;
  244. /// <summary>
  245. /// Creates gRPC environment.
  246. /// </summary>
  247. private GrpcEnvironment()
  248. {
  249. GrpcNativeInit();
  250. batchContextPool = new DefaultObjectPool<BatchContextSafeHandle>(() => BatchContextSafeHandle.Create(this.batchContextPool), batchContextPoolSharedCapacity, batchContextPoolThreadLocalCapacity);
  251. threadPool = new GrpcThreadPool(this, GetThreadPoolSizeOrDefault(), GetCompletionQueueCountOrDefault(), inlineHandlers);
  252. threadPool.Start();
  253. }
  254. /// <summary>
  255. /// Gets the completion queues used by this gRPC environment.
  256. /// </summary>
  257. internal IReadOnlyCollection<CompletionQueueSafeHandle> CompletionQueues
  258. {
  259. get
  260. {
  261. return this.threadPool.CompletionQueues;
  262. }
  263. }
  264. internal IObjectPool<BatchContextSafeHandle> BatchContextPool => batchContextPool;
  265. internal bool IsAlive
  266. {
  267. get
  268. {
  269. return this.threadPool.IsAlive;
  270. }
  271. }
  272. /// <summary>
  273. /// Picks a completion queue in a round-robin fashion.
  274. /// Shouldn't be invoked on a per-call basis (used at per-channel basis).
  275. /// </summary>
  276. internal CompletionQueueSafeHandle PickCompletionQueue()
  277. {
  278. var cqIndex = (int) ((cqPickerCounter.Increment() - 1) % this.threadPool.CompletionQueues.Count);
  279. return this.threadPool.CompletionQueues.ElementAt(cqIndex);
  280. }
  281. /// <summary>
  282. /// Gets the completion queue used by this gRPC environment.
  283. /// </summary>
  284. internal DebugStats DebugStats
  285. {
  286. get
  287. {
  288. return this.debugStats;
  289. }
  290. }
  291. /// <summary>
  292. /// Gets version of gRPC C core.
  293. /// </summary>
  294. internal static string GetCoreVersionString()
  295. {
  296. var ptr = NativeMethods.Get().grpcsharp_version_string(); // the pointer is not owned
  297. return Marshal.PtrToStringAnsi(ptr);
  298. }
  299. internal static void GrpcNativeInit()
  300. {
  301. NativeMethods.Get().grpcsharp_init();
  302. }
  303. internal static void GrpcNativeShutdown()
  304. {
  305. NativeMethods.Get().grpcsharp_shutdown();
  306. }
  307. /// <summary>
  308. /// Shuts down this environment.
  309. /// </summary>
  310. private async Task ShutdownAsync()
  311. {
  312. if (isShutdown)
  313. {
  314. throw new InvalidOperationException("ShutdownAsync has already been called");
  315. }
  316. await Task.Run(() => ShuttingDown?.Invoke(this, null)).ConfigureAwait(false);
  317. await threadPool.StopAsync().ConfigureAwait(false);
  318. batchContextPool.Dispose();
  319. GrpcNativeShutdown();
  320. isShutdown = true;
  321. debugStats.CheckOK();
  322. }
  323. private int GetThreadPoolSizeOrDefault()
  324. {
  325. if (customThreadPoolSize.HasValue)
  326. {
  327. return customThreadPoolSize.Value;
  328. }
  329. // In systems with many cores, use half of the cores for GrpcThreadPool
  330. // and the other half for .NET thread pool. This heuristic definitely needs
  331. // more work, but seems to work reasonably well for a start.
  332. return Math.Max(MinDefaultThreadPoolSize, Environment.ProcessorCount / 2);
  333. }
  334. private int GetCompletionQueueCountOrDefault()
  335. {
  336. if (customCompletionQueueCount.HasValue)
  337. {
  338. return customCompletionQueueCount.Value;
  339. }
  340. // by default, create a completion queue for each thread
  341. return GetThreadPoolSizeOrDefault();
  342. }
  343. private static class ShutdownHooks
  344. {
  345. static object staticLock = new object();
  346. static bool hooksRegistered;
  347. public static void Register()
  348. {
  349. lock (staticLock)
  350. {
  351. if (!hooksRegistered)
  352. {
  353. #if NETSTANDARD1_5
  354. System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += (assemblyLoadContext) => { HandleShutdown(); };
  355. #else
  356. AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => { HandleShutdown(); };
  357. AppDomain.CurrentDomain.DomainUnload += (sender, eventArgs) => { HandleShutdown(); };
  358. #endif
  359. }
  360. hooksRegistered = true;
  361. }
  362. }
  363. /// <summary>
  364. /// Handler for AppDomain.DomainUnload, AppDomain.ProcessExit and AssemblyLoadContext.Unloading hooks.
  365. /// </summary>
  366. private static void HandleShutdown()
  367. {
  368. Task.WaitAll(GrpcEnvironment.ShutdownChannelsAsync(), GrpcEnvironment.KillServersAsync());
  369. }
  370. }
  371. }
  372. }