PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/PushSharp.Common/PushServiceBase.cs

https://github.com/mustafagenc/PushSharp
C# | 250 lines | 191 code | 49 blank | 10 comment | 47 complexity | 701b81bba5b08491f793a73201b37925 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Concurrent;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. namespace PushSharp.Common
  9. {
  10. public abstract class PushServiceBase<TChannelSettings> : IDisposable where TChannelSettings : PushChannelSettings
  11. {
  12. public ChannelEvents Events = new ChannelEvents();
  13. public abstract PlatformType Platform { get; }
  14. public PushServiceSettings ServiceSettings { get; private set; }
  15. public TChannelSettings ChannelSettings { get; private set; }
  16. public bool IsStopping { get { return stopping; } }
  17. Timer timerCheckScale;
  18. Task distributerTask;
  19. bool stopping;
  20. List<PushChannelBase> channels = new List<PushChannelBase>();
  21. ConcurrentQueue<Notification> queuedNotifications = new ConcurrentQueue<Notification>();
  22. CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
  23. List<double> measurements = new List<double>();
  24. protected abstract PushChannelBase CreateChannel(PushChannelSettings channelSettings);
  25. public PushServiceBase(TChannelSettings channelSettings, PushServiceSettings serviceSettings = null)
  26. {
  27. this.ServiceSettings = serviceSettings ?? new PushServiceSettings();
  28. this.ChannelSettings = channelSettings;
  29. this.queuedNotifications = new ConcurrentQueue<Notification>();
  30. timerCheckScale = new Timer(new TimerCallback((state) =>
  31. {
  32. CheckScale();
  33. }), null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15));
  34. CheckScale();
  35. distributerTask = new Task(Distributer, TaskCreationOptions.LongRunning);
  36. distributerTask.ContinueWith((ft) =>
  37. {
  38. var ex = ft.Exception;
  39. }, TaskContinuationOptions.OnlyOnFaulted);
  40. distributerTask.Start();
  41. stopping = false;
  42. }
  43. public void QueueNotification(Notification notification)
  44. {
  45. notification.EnqueuedTimestamp = DateTime.UtcNow;
  46. queuedNotifications.Enqueue(notification);
  47. }
  48. public void Stop(bool waitForQueueToFinish)
  49. {
  50. stopping = true;
  51. //Stop the timer for checking scale
  52. if (this.timerCheckScale != null)
  53. this.timerCheckScale.Change(Timeout.Infinite, Timeout.Infinite);
  54. //Stop all channels
  55. Parallel.ForEach<PushChannelBase>(channels,
  56. (channel) =>
  57. {
  58. channel.Stop(waitForQueueToFinish);
  59. channel.Events.UnRegisterProxyHandler(this.Events);
  60. });
  61. this.channels.Clear();
  62. //Sleep a bit to avoid race conditions
  63. Thread.Sleep(2000);
  64. this.cancelTokenSource.Cancel();
  65. }
  66. public void Dispose()
  67. {
  68. if (!stopping)
  69. Stop(false);
  70. }
  71. void Distributer()
  72. {
  73. while (!this.cancelTokenSource.IsCancellationRequested)
  74. {
  75. if (channels == null || channels.Count <= 0)
  76. {
  77. Thread.Sleep(250);
  78. continue;
  79. }
  80. Notification notification = null;
  81. if (!queuedNotifications.TryDequeue(out notification))
  82. {
  83. //No notifications in queue, sleep a bit!
  84. Thread.Sleep(250);
  85. continue;
  86. }
  87. PushChannelBase channelOn = null;
  88. //Get the channel with the smallest queue
  89. if (channels.Count == 1)
  90. channelOn = channels[0];
  91. else
  92. channelOn = (from c in channels
  93. orderby c.QueuedNotificationCount
  94. select c).FirstOrDefault();
  95. if (channelOn != null)
  96. {
  97. //Measure when the message entered the queue
  98. notification.EnqueuedTimestamp = DateTime.UtcNow;
  99. channelOn.QueueNotification(notification);
  100. }
  101. }
  102. }
  103. void CheckScale()
  104. {
  105. if (ServiceSettings.AutoScaleChannels && !this.cancelTokenSource.IsCancellationRequested && !stopping)
  106. {
  107. if (channels.Count <= 0)
  108. {
  109. ScaleChannels(ChannelScaleAction.Create);
  110. return;
  111. }
  112. var avgTime = GetAverageQueueWait();
  113. if (avgTime < 100 && channels.Count > 1)
  114. {
  115. ScaleChannels(ChannelScaleAction.Destroy);
  116. }
  117. else if (channels.Count < this.ServiceSettings.MaxAutoScaleChannels)
  118. {
  119. var numChannelsToSpinUp = 1;
  120. //Depending on the wait time, let's spin up more than 1 channel at a time
  121. if (avgTime > 5000)
  122. numChannelsToSpinUp = 5;
  123. else if (avgTime > 1000)
  124. numChannelsToSpinUp = 2;
  125. else if (avgTime > 100)
  126. numChannelsToSpinUp = 1;
  127. ScaleChannels(ChannelScaleAction.Create, numChannelsToSpinUp);
  128. }
  129. }
  130. else
  131. {
  132. while (channels.Count > ServiceSettings.Channels && !this.cancelTokenSource.IsCancellationRequested && !stopping)
  133. ScaleChannels(ChannelScaleAction.Destroy);
  134. while (channels.Count < ServiceSettings.Channels && !this.cancelTokenSource.IsCancellationRequested && !stopping)
  135. ScaleChannels(ChannelScaleAction.Create);
  136. }
  137. }
  138. void newChannel_OnQueueTimed(double queueTimeMilliseconds)
  139. {
  140. //We got a measurement for how long a message waited in the queue
  141. lock (measurements)
  142. {
  143. measurements.Add(queueTimeMilliseconds);
  144. //Remove old measurements
  145. while (measurements.Count > 1000)
  146. measurements.RemoveAt(0);
  147. }
  148. }
  149. double GetAverageQueueWait()
  150. {
  151. if (measurements == null)
  152. return 0;
  153. lock (measurements)
  154. {
  155. if (measurements.Count > 0)
  156. return measurements.Average();
  157. else
  158. return 0;
  159. }
  160. }
  161. void ScaleChannels(ChannelScaleAction action, int count = 1)
  162. {
  163. for (int i = 0; i < count; i++)
  164. {
  165. var newCount = 0;
  166. bool? destroyed= null;
  167. lock (channels)
  168. {
  169. if (action == ChannelScaleAction.Create)
  170. {
  171. var newChannel = this.CreateChannel(this.ChannelSettings);
  172. newChannel.Events.RegisterProxyHandler(this.Events);
  173. newChannel.OnQueueTimed += new Action<double>(newChannel_OnQueueTimed);
  174. channels.Add(newChannel);
  175. newCount = channels.Count;
  176. destroyed = false;
  177. }
  178. else if (action == ChannelScaleAction.Destroy && channels.Count > 1)
  179. {
  180. var channelOn = channels[0];
  181. channels.RemoveAt(0);
  182. //Now stop the channel but let it finish
  183. channelOn.Stop(true);
  184. channelOn.Events.UnRegisterProxyHandler(this.Events);
  185. newCount = channels.Count;
  186. destroyed = true;
  187. }
  188. }
  189. if (destroyed.HasValue && !destroyed.Value)
  190. this.Events.RaiseChannelCreated(this.Platform, newCount);
  191. else if (destroyed.HasValue && destroyed.Value)
  192. this.Events.RaiseChannelDestroyed(this.Platform, newCount);
  193. }
  194. }
  195. }
  196. public enum ChannelScaleAction
  197. {
  198. Create,
  199. Destroy
  200. }
  201. }