PageRenderTime 54ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Alpha/Source/Libraries/GSF.SortedTreeStore/Collections/IsolatedQueueFileBacked`1.cs

#
C# | 358 lines | 219 code | 34 blank | 105 comment | 30 complexity | bad6e5e6e63ba3af1baf1b3fe70105e2 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, EPL-1.0
  1. //******************************************************************************************************
  2. // IsolatedQueueFileBacked.cs - Gbtc
  3. //
  4. // Copyright © 2013, Grid Protection Alliance. All Rights Reserved.
  5. //
  6. // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
  7. // the NOTICE file distributed with this work for additional information regarding copyright ownership.
  8. // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
  9. // not use this file except in compliance with the License. You may obtain a copy of the License at:
  10. //
  11. // http://www.opensource.org/licenses/eclipse-1.0.php
  12. //
  13. // Unless agreed to in writing, the subject software distributed under the License is distributed on an
  14. // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
  15. // License for the specific language governing permissions and limitations.
  16. //
  17. // Code Modification History:
  18. // ----------------------------------------------------------------------------------------------------
  19. // 1/4/2013 - Steven E. Chisholm
  20. // Generated original version of source code.
  21. //
  22. //******************************************************************************************************
  23. using System;
  24. using System.IO;
  25. using GSF.Threading;
  26. namespace GSF.Collections
  27. {
  28. /// <summary>
  29. /// An interface required to use <see cref="IsolatedQueueFileBacked{T}"/>.
  30. /// </summary>
  31. public interface ILoadable
  32. {
  33. /// <summary>
  34. /// Gets the average memory size required for an individual element.
  35. /// </summary>
  36. int InMemorySize
  37. {
  38. get;
  39. }
  40. /// <summary>
  41. /// Gets the average space this individual element takes when serialized to the disk.
  42. /// </summary>
  43. int OnDiskSize
  44. {
  45. get;
  46. }
  47. /// <summary>
  48. /// Saves this element using the provided <see cref="BinaryWriter"/>.
  49. /// </summary>
  50. /// <param name="writer"></param>
  51. void Save(BinaryWriter writer);
  52. /// <summary>
  53. /// Loads this element using the provided <see cref="BinaryReader"/>.
  54. /// </summary>
  55. /// <param name="reader"></param>
  56. void Load(BinaryReader reader);
  57. }
  58. //ToDo: Don't let the diskIO operations apply a lock on the entire Enqueue thread.
  59. /// <summary>
  60. /// Provides a high speed queue that has built in isolation of reads from writes.
  61. /// This means, reads must be synchronized with other reads, writes must be synchronized with other writes,
  62. /// however read and write operations do not have to be synchronized.
  63. /// </summary>
  64. /// <typeparam name="T">A struct that implements <see cref="ILoadable"/> that will be stored in this queue.</typeparam>
  65. public partial class IsolatedQueueFileBacked<T> : IDisposable
  66. where T : struct, ILoadable
  67. {
  68. /// <summary>
  69. /// The queue that the writer always writes to.
  70. /// This queue is only read from if not operating in FileMode
  71. /// </summary>
  72. private readonly ContinuousQueue<IsolatedNode<T>> m_inboundQueue;
  73. /// <summary>
  74. /// This queue is where the reader will read from when
  75. /// operating in FileMode.
  76. /// </summary>
  77. private readonly ContinuousQueue<IsolatedNode<T>> m_outboundQueue;
  78. /// <summary>
  79. /// Contains a queue of <see cref="IsolatedNode{T}"/> so they
  80. /// don't need to be constructed every time. There is a high
  81. /// probability that any node will be a Generation 2 object. Therefore
  82. /// it is advised to pool these objects.
  83. /// </summary>
  84. private readonly ResourceQueue<IsolatedNode<T>> m_pooledNodes;
  85. /// <summary>
  86. /// The node that will be written to. It is probable that the
  87. /// head and tail are the same instance.
  88. /// </summary>
  89. private IsolatedNode<T> m_currentHead;
  90. /// <summary>
  91. /// The node that will be read from. It is probable that the
  92. /// head and tail are the same instance.
  93. /// </summary>
  94. private IsolatedNode<T> m_currentTail;
  95. private readonly ScheduledTask m_workerFlushToFile;
  96. private bool m_disposing;
  97. private bool m_disposed;
  98. private bool m_isFileMode;
  99. private bool m_currentlyWritingFile;
  100. private readonly int m_elementsPerNode;
  101. private readonly object m_syncRoot;
  102. /// <summary>
  103. /// Number of nodes that it takes to max out the memory desired for this buffer.
  104. /// </summary>
  105. private readonly int m_maxNodeCount;
  106. /// <summary>
  107. /// The number of nodes to put in each file.
  108. /// </summary>
  109. private readonly int m_nodesPerFile;
  110. private readonly FileIO m_fileIO;
  111. /// <summary>
  112. /// Creates a new <see cref="IsolatedQueueFileBacked{T}"/>.
  113. /// </summary>
  114. /// <param name="path">The disk path to use to save the state of this queue to.
  115. /// It is critical that this path and file prefix is unique to the instance of this class.</param>
  116. /// <param name="filePrefix">The prefix string to add to the beginning of every file in this directory.</param>
  117. /// <param name="maxInMemorySize">The maximum desired in-memory size before switching to a file storage method.</param>
  118. /// <param name="individualFileSize">The desired size of each file.</param>
  119. /// <remarks>The total memory used by this class will be approximately the sum of <see cref="maxInMemorySize"/> and
  120. /// <see cref="individualFileSize"/> while operating in file mode.</remarks>
  121. public IsolatedQueueFileBacked(string path, string filePrefix, int maxInMemorySize, int individualFileSize)
  122. {
  123. m_fileIO = new FileIO(path, filePrefix);
  124. m_isFileMode = (m_fileIO.FileCount > 0);
  125. m_elementsPerNode = 1024;
  126. m_pooledNodes = new ResourceQueue<IsolatedNode<T>>(() => new IsolatedNode<T>(m_elementsPerNode), 2, 10);
  127. m_inboundQueue = new ContinuousQueue<IsolatedNode<T>>();
  128. m_outboundQueue = new ContinuousQueue<IsolatedNode<T>>();
  129. m_workerFlushToFile = new ScheduledTask(ThreadingMode.DedicatedForeground);
  130. m_workerFlushToFile.OnRunWorker += OnWorkerFlushToFileDoWork;
  131. m_workerFlushToFile.OnDispose += OnWorkerFlushToFileCleanupWork;
  132. m_syncRoot = new object();
  133. T value = default(T);
  134. m_maxNodeCount = maxInMemorySize / value.InMemorySize / m_elementsPerNode;
  135. m_nodesPerFile = individualFileSize / value.OnDiskSize / m_elementsPerNode;
  136. m_nodesPerFile = Math.Max(m_nodesPerFile, 10);
  137. m_maxNodeCount = Math.Max(m_maxNodeCount, m_nodesPerFile);
  138. }
  139. /// <summary>
  140. /// Gets if this object has been disposed.
  141. /// </summary>
  142. public bool IsDisposed
  143. {
  144. get
  145. {
  146. return m_disposed;
  147. }
  148. }
  149. /// <summary>
  150. /// Does the writes to the archive file.
  151. /// </summary>
  152. private void OnWorkerFlushToFileDoWork(object sender, ScheduledTaskEventArgs scheduledTaskEventArgs)
  153. {
  154. while (true)
  155. {
  156. IsolatedNode<T>[] nodesToWrite;
  157. lock (m_syncRoot)
  158. {
  159. if (!ShouldFlushToFile())
  160. return;
  161. m_isFileMode = true;
  162. nodesToWrite = m_inboundQueue.Dequeue(m_nodesPerFile);
  163. m_currentlyWritingFile = true;
  164. }
  165. m_fileIO.DumpToDisk(nodesToWrite);
  166. lock (m_syncRoot)
  167. {
  168. m_currentlyWritingFile = false;
  169. }
  170. }
  171. }
  172. private bool ShouldFlushToFile()
  173. {
  174. if (m_isFileMode)
  175. {
  176. return m_inboundQueue.Count > m_nodesPerFile;
  177. }
  178. return m_inboundQueue.Count > m_maxNodeCount;
  179. }
  180. private void OnWorkerFlushToFileCleanupWork(object sender, ScheduledTaskEventArgs scheduledTaskEventArgs)
  181. {
  182. while (m_inboundQueue.Count >= m_nodesPerFile)
  183. {
  184. IsolatedNode<T>[] nodesToWrite = m_inboundQueue.Dequeue(m_nodesPerFile);
  185. m_fileIO.DumpToDisk(nodesToWrite);
  186. }
  187. if (m_inboundQueue.Count > 0)
  188. {
  189. IsolatedNode<T>[] nodesToWrite = m_inboundQueue.Dequeue(m_inboundQueue.Count);
  190. m_fileIO.DumpToDisk(nodesToWrite);
  191. }
  192. if (m_currentTail != null && m_currentTail.Count > 0)
  193. {
  194. m_outboundQueue.AddToTail(m_currentTail);
  195. }
  196. if (m_outboundQueue.Count > 0)
  197. {
  198. IsolatedNode<T>[] nodesToWrite = m_outboundQueue.Dequeue(m_outboundQueue.Count);
  199. m_fileIO.DumpToDisk(nodesToWrite, false);
  200. }
  201. }
  202. /// <summary>
  203. /// Writes data to the queue. Will not block
  204. /// </summary>
  205. /// <param name="item"></param>
  206. public void Enqueue(T item)
  207. {
  208. if (m_disposing)
  209. throw new ObjectDisposedException(GetType().FullName);
  210. if (m_currentHead == null || m_currentHead.IsHeadFull)
  211. {
  212. //This can be done without a lock since it will be checked in the worker.
  213. if (ShouldFlushToFile())
  214. {
  215. m_workerFlushToFile.Start();
  216. }
  217. m_currentHead = m_pooledNodes.Dequeue();
  218. m_currentHead.Reset();
  219. lock (m_syncRoot)
  220. {
  221. m_inboundQueue.Enqueue(m_currentHead);
  222. }
  223. }
  224. m_currentHead.Enqueue(item);
  225. }
  226. public bool TryDequeue(out T item)
  227. {
  228. if (m_disposing)
  229. throw new ObjectDisposedException(GetType().FullName);
  230. if (m_currentTail == null || m_currentTail.IsTailFull)
  231. {
  232. if (m_currentTail != null)
  233. {
  234. //Don't reset the node on return since it is still
  235. //possible for the enqueue thread to be using it.
  236. //Note: If the enqueue thread pulls it off the queue
  237. //immediately, this is ok since it will be coordinated at that point.
  238. m_pooledNodes.Enqueue(m_currentTail);
  239. }
  240. bool repeat = true;
  241. bool success = false; //initialization not necessary. Just trying to get rid of a compilier warning.
  242. while (repeat)
  243. {
  244. repeat = false;
  245. bool readFromDisk = false;
  246. lock (m_syncRoot)
  247. {
  248. if (m_isFileMode)
  249. {
  250. if (m_outboundQueue.Count > 0)
  251. {
  252. m_currentTail = m_outboundQueue.Dequeue();
  253. success = true;
  254. }
  255. else
  256. {
  257. if (m_fileIO.FileCount == 0)
  258. {
  259. if (m_currentlyWritingFile)
  260. {
  261. m_currentTail = null;
  262. item = default(T);
  263. return false;
  264. }
  265. m_isFileMode = false;
  266. }
  267. else
  268. {
  269. readFromDisk = true;
  270. }
  271. repeat = true;
  272. }
  273. }
  274. else
  275. {
  276. if (m_inboundQueue.Count > 0)
  277. {
  278. m_currentTail = m_inboundQueue.Dequeue();
  279. success = true;
  280. }
  281. else
  282. {
  283. success = false;
  284. }
  285. }
  286. }
  287. if (readFromDisk)
  288. {
  289. m_fileIO.ReadFromDisk(m_outboundQueue, () => m_pooledNodes.Dequeue());
  290. }
  291. }
  292. if (!success)
  293. {
  294. m_currentTail = null;
  295. item = default(T);
  296. return false;
  297. }
  298. }
  299. return m_currentTail.TryDequeue(out item);
  300. }
  301. /// <summary>
  302. /// Since the math for the count is pretty complex, this only gives an idea of the
  303. /// size of the structure. In other words, a value of zero does not mean this list is empty.
  304. /// </summary>
  305. public long EstimateCount
  306. {
  307. get
  308. {
  309. //ToDo: properly calculate the number of elements.
  310. //This should also include the files pending loading.
  311. return m_inboundQueue.Count * (long)m_elementsPerNode;
  312. }
  313. }
  314. public void Dispose()
  315. {
  316. if (!m_disposed)
  317. {
  318. m_disposing = true;
  319. m_workerFlushToFile.Dispose();
  320. m_disposed = true;
  321. }
  322. }
  323. }
  324. }