History

Renamed folders and separated GSF.Media and GSF.Net into separate projects.

StephenCWills committed 14 years ago

Commit ac1ef661 parent 45bce55628 files · +733 −461Browse files

Showing the changes to Source/Libraries/GSF.Core/IO/InterprocessCache.cs (1 of 628 files). Show all

Unified · Split

Source/Libraries/GSF.Core/IO/InterprocessCache.cs+923−0
@@ -0,0 +1,923 @@
1+//*******************************************************************************************************
2+// InterprocessCache.cs - Gbtc
3+//
4+// Tennessee Valley Authority, 2011
5+// No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved.
6+//
7+// This software is made freely available under the TVA Open Source Agreement (see below).
8+// Code in this file licensed to GSF under one or more contributor license agreements listed below.
9+//
10+// Code Modification History:
11+// -----------------------------------------------------------------------------------------------------
12+// 03/21/2011 - J. Ritchie Carroll
13+// Generated original version of source code.
14+// 04/06/2011 - J. Ritchie Carroll
15+// Added Flush() method to block current thread and wait for any pending save to complete.
16+//
17+//*******************************************************************************************************
18+
19+#region [ TVA Open Source Agreement ]
20+/*
21+
22+ THIS OPEN SOURCE AGREEMENT ("AGREEMENT") DEFINES THE RIGHTS OF USE,REPRODUCTION, DISTRIBUTION,
23+ MODIFICATION AND REDISTRIBUTION OF CERTAIN COMPUTER SOFTWARE ORIGINALLY RELEASED BY THE
24+ TENNESSEE VALLEY AUTHORITY, A CORPORATE AGENCY AND INSTRUMENTALITY OF THE UNITED STATES GOVERNMENT
25+ ("GOVERNMENT AGENCY"). GOVERNMENT AGENCY IS AN INTENDED THIRD-PARTY BENEFICIARY OF ALL SUBSEQUENT
26+ DISTRIBUTIONS OR REDISTRIBUTIONS OF THE SUBJECT SOFTWARE. ANYONE WHO USES, REPRODUCES, DISTRIBUTES,
27+ MODIFIES OR REDISTRIBUTES THE SUBJECT SOFTWARE, AS DEFINED HEREIN, OR ANY PART THEREOF, IS, BY THAT
28+ ACTION, ACCEPTING IN FULL THE RESPONSIBILITIES AND OBLIGATIONS CONTAINED IN THIS AGREEMENT.
29+
30+ Original Software Designation: openPDC
31+ Original Software Title: The GSF Open Source Phasor Data Concentrator
32+ User Registration Requested. Please Visit https://naspi.tva.com/Registration/
33+ Point of Contact for Original Software: J. Ritchie Carroll <mailto:jrcarrol@tva.gov>
34+
35+ 1. DEFINITIONS
36+
37+ A. "Contributor" means Government Agency, as the developer of the Original Software, and any entity
38+ that makes a Modification.
39+
40+ B. "Covered Patents" mean patent claims licensable by a Contributor that are necessarily infringed by
41+ the use or sale of its Modification alone or when combined with the Subject Software.
42+
43+ C. "Display" means the showing of a copy of the Subject Software, either directly or by means of an
44+ image, or any other device.
45+
46+ D. "Distribution" means conveyance or transfer of the Subject Software, regardless of means, to
47+ another.
48+
49+ E. "Larger Work" means computer software that combines Subject Software, or portions thereof, with
50+ software separate from the Subject Software that is not governed by the terms of this Agreement.
51+
52+ F. "Modification" means any alteration of, including addition to or deletion from, the substance or
53+ structure of either the Original Software or Subject Software, and includes derivative works, as that
54+ term is defined in the Copyright Statute, 17 USC § 101. However, the act of including Subject Software
55+ as part of a Larger Work does not in and of itself constitute a Modification.
56+
57+ G. "Original Software" means the computer software first released under this Agreement by Government
58+ Agency entitled openPDC, including source code, object code and accompanying documentation, if any.
59+
60+ H. "Recipient" means anyone who acquires the Subject Software under this Agreement, including all
61+ Contributors.
62+
63+ I. "Redistribution" means Distribution of the Subject Software after a Modification has been made.
64+
65+ J. "Reproduction" means the making of a counterpart, image or copy of the Subject Software.
66+
67+ K. "Sale" means the exchange of the Subject Software for money or equivalent value.
68+
69+ L. "Subject Software" means the Original Software, Modifications, or any respective parts thereof.
70+
71+ M. "Use" means the application or employment of the Subject Software for any purpose.
72+
73+ 2. GRANT OF RIGHTS
74+
75+ A. Under Non-Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor,
76+ with respect to its own contribution to the Subject Software, hereby grants to each Recipient a
77+ non-exclusive, world-wide, royalty-free license to engage in the following activities pertaining to
78+ the Subject Software:
79+
80+ 1. Use
81+
82+ 2. Distribution
83+
84+ 3. Reproduction
85+
86+ 4. Modification
87+
88+ 5. Redistribution
89+
90+ 6. Display
91+
92+ B. Under Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor, with
93+ respect to its own contribution to the Subject Software, hereby grants to each Recipient under Covered
94+ Patents a non-exclusive, world-wide, royalty-free license to engage in the following activities
95+ pertaining to the Subject Software:
96+
97+ 1. Use
98+
99+ 2. Distribution
100+
101+ 3. Reproduction
102+
103+ 4. Sale
104+
105+ 5. Offer for Sale
106+
107+ C. The rights granted under Paragraph B. also apply to the combination of a Contributor's Modification
108+ and the Subject Software if, at the time the Modification is added by the Contributor, the addition of
109+ such Modification causes the combination to be covered by the Covered Patents. It does not apply to
110+ any other combinations that include a Modification.
111+
112+ D. The rights granted in Paragraphs A. and B. allow the Recipient to sublicense those same rights.
113+ Such sublicense must be under the same terms and conditions of this Agreement.
114+
115+ 3. OBLIGATIONS OF RECIPIENT
116+
117+ A. Distribution or Redistribution of the Subject Software must be made under this Agreement except for
118+ additions covered under paragraph 3H.
119+
120+ 1. Whenever a Recipient distributes or redistributes the Subject Software, a copy of this Agreement
121+ must be included with each copy of the Subject Software; and
122+
123+ 2. If Recipient distributes or redistributes the Subject Software in any form other than source code,
124+ Recipient must also make the source code freely available, and must provide with each copy of the
125+ Subject Software information on how to obtain the source code in a reasonable manner on or through a
126+ medium customarily used for software exchange.
127+
128+ B. Each Recipient must ensure that the following copyright notice appears prominently in the Subject
129+ Software:
130+
131+ No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved.
132+
133+ C. Each Contributor must characterize its alteration of the Subject Software as a Modification and
134+ must identify itself as the originator of its Modification in a manner that reasonably allows
135+ subsequent Recipients to identify the originator of the Modification. In fulfillment of these
136+ requirements, Contributor must include a file (e.g., a change log file) that describes the alterations
137+ made and the date of the alterations, identifies Contributor as originator of the alterations, and
138+ consents to characterization of the alterations as a Modification, for example, by including a
139+ statement that the Modification is derived, directly or indirectly, from Original Software provided by
140+ Government Agency. Once consent is granted, it may not thereafter be revoked.
141+
142+ D. A Contributor may add its own copyright notice to the Subject Software. Once a copyright notice has
143+ been added to the Subject Software, a Recipient may not remove it without the express permission of
144+ the Contributor who added the notice.
145+
146+ E. A Recipient may not make any representation in the Subject Software or in any promotional,
147+ advertising or other material that may be construed as an endorsement by Government Agency or by any
148+ prior Recipient of any product or service provided by Recipient, or that may seek to obtain commercial
149+ advantage by the fact of Government Agency's or a prior Recipient's participation in this Agreement.
150+
151+ F. In an effort to track usage and maintain accurate records of the Subject Software, each Recipient,
152+ upon receipt of the Subject Software, is requested to register with Government Agency by visiting the
153+ following website: https://naspi.tva.com/Registration/. Recipient's name and personal information
154+ shall be used for statistical purposes only. Once a Recipient makes a Modification available, it is
155+ requested that the Recipient inform Government Agency at the web site provided above how to access the
156+ Modification.
157+
158+ G. Each Contributor represents that that its Modification does not violate any existing agreements,
159+ regulations, statutes or rules, and further that Contributor has sufficient rights to grant the rights
160+ conveyed by this Agreement.
161+
162+ H. A Recipient may choose to offer, and to charge a fee for, warranty, support, indemnity and/or
163+ liability obligations to one or more other Recipients of the Subject Software. A Recipient may do so,
164+ however, only on its own behalf and not on behalf of Government Agency or any other Recipient. Such a
165+ Recipient must make it absolutely clear that any such warranty, support, indemnity and/or liability
166+ obligation is offered by that Recipient alone. Further, such Recipient agrees to indemnify Government
167+ Agency and every other Recipient for any liability incurred by them as a result of warranty, support,
168+ indemnity and/or liability offered by such Recipient.
169+
170+ I. A Recipient may create a Larger Work by combining Subject Software with separate software not
171+ governed by the terms of this agreement and distribute the Larger Work as a single product. In such
172+ case, the Recipient must make sure Subject Software, or portions thereof, included in the Larger Work
173+ is subject to this Agreement.
174+
175+ J. Notwithstanding any provisions contained herein, Recipient is hereby put on notice that export of
176+ any goods or technical data from the United States may require some form of export license from the
177+ U.S. Government. Failure to obtain necessary export licenses may result in criminal liability under
178+ U.S. laws. Government Agency neither represents that a license shall not be required nor that, if
179+ required, it shall be issued. Nothing granted herein provides any such export license.
180+
181+ 4. DISCLAIMER OF WARRANTIES AND LIABILITIES; WAIVER AND INDEMNIFICATION
182+
183+ A. No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER
184+ EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SUBJECT
185+ SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
186+ PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR
187+ FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS
188+ AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR
189+ RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
190+ RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND
191+ LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT
192+ "AS IS."
193+
194+ B. Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST GOVERNMENT AGENCY, ITS
195+ AGENTS, EMPLOYEES, CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF RECIPIENT'S USE
196+ OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM
197+ SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE OF THE
198+ SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS GOVERNMENT AGENCY, ITS AGENTS,
199+ EMPLOYEES, CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY
200+ LAW. THE FOREGOING RELEASE AND INDEMNIFICATION SHALL APPLY EVEN IF THE LIABILITIES, DEMANDS, DAMAGES,
201+ EXPENSES OR LOSSES ARE CAUSED, OCCASIONED, OR CONTRIBUTED TO BY THE NEGLIGENCE, SOLE OR CONCURRENT, OF
202+ GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE
203+ IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
204+
205+ 5. GENERAL TERMS
206+
207+ A. Termination: This Agreement and the rights granted hereunder will terminate automatically if a
208+ Recipient fails to comply with these terms and conditions, and fails to cure such noncompliance within
209+ thirty (30) days of becoming aware of such noncompliance. Upon termination, a Recipient agrees to
210+ immediately cease use and distribution of the Subject Software. All sublicenses to the Subject
211+ Software properly granted by the breaching Recipient shall survive any such termination of this
212+ Agreement.
213+
214+ B. Severability: If any provision of this Agreement is invalid or unenforceable under applicable law,
215+ it shall not affect the validity or enforceability of the remainder of the terms of this Agreement.
216+
217+ C. Applicable Law: This Agreement shall be subject to United States federal law only for all purposes,
218+ including, but not limited to, determining the validity of this Agreement, the meaning of its
219+ provisions and the rights, obligations and remedies of the parties.
220+
221+ D. Entire Understanding: This Agreement constitutes the entire understanding and agreement of the
222+ parties relating to release of the Subject Software and may not be superseded, modified or amended
223+ except by further written agreement duly executed by the parties.
224+
225+ E. Binding Authority: By accepting and using the Subject Software under this Agreement, a Recipient
226+ affirms its authority to bind the Recipient to all terms and conditions of this Agreement and that
227+ Recipient hereby agrees to all terms and conditions herein.
228+
229+ F. Point of Contact: Any Recipient contact with Government Agency is to be directed to the designated
230+ representative as follows: J. Ritchie Carroll <mailto:jrcarrol@tva.gov>.
231+
232+*/
233+#endregion
234+
235+#region [ Contributor License Agreements ]
236+
237+//******************************************************************************************************
238+//
239+// Copyright © 2011, Grid Protection Alliance. All Rights Reserved.
240+//
241+// The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
242+// not use this file except in compliance with the License. You may obtain a copy of the License at:
243+//
244+// http://www.opensource.org/licenses/eclipse-1.0.php
245+//
246+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
247+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
248+// License for the specific language governing permissions and limitations.
249+//
250+//******************************************************************************************************
251+
252+#endregion
253+
254+using GSF.Collections;
255+using GSF.Threading;
256+using System;
257+using System.Collections;
258+using System.IO;
259+using System.Threading;
260+
261+namespace GSF.IO
262+{
263+ /// <summary>
264+ /// Represents a serialized data cache that can be saved or read from multiple applications using interprocess synchronization.
265+ /// </summary>
266+ /// <remarks>
267+ /// Note that all file data in this class gets serialized to and from memory, as such, the design intention for this class is for
268+ /// use with smaller data sets such as serialized lists or dictionaries that need interprocess synchronized loading and saving.
269+ /// </remarks>
270+ public class InterprocessCache : IDisposable
271+ {
272+ #region [ Members ]
273+
274+ // Constants
275+ private const int WriteEvent = 0;
276+ private const int ReadEvent = 1;
277+
278+ /// <summary>
279+ /// Default maximum retry attempts allowed for loading <see cref="InterprocessCache"/>.
280+ /// </summary>
281+ public const int DefaultMaximumRetryAttempts = 10;
282+
283+ /// <summary>
284+ /// Default wait interval, in milliseconds, before retrying load of <see cref="InterprocessCache"/>.
285+ /// </summary>
286+ public const double DefaultRetryDelayInterval = 200.0D;
287+
288+ // Fields
289+ private string m_fileName; // Path and file name of file needing interprocess synchronization
290+ private byte[] m_fileData; // Data loaded or to be saved
291+ private bool m_autoSave; // Flag to auto save when file data has changed
292+ private InterprocessReaderWriterLock m_fileLock; // Interprocess reader/writer lock used to synchronize file access
293+ private ReaderWriterLockSlim m_dataLock; // Thread level reader/writer lock used to synchronize file data access
294+ private ManualResetEventSlim m_loadIsReady; // Wait handle used so that system will wait for file data load
295+ private ManualResetEventSlim m_saveIsReady; // Wait handle used so that system will wait for file data save
296+ private FileSystemWatcher m_fileWatcher; // Optional file watcher used to reload changes
297+ private int m_maximumConcurrentLocks; // Maximum concurrent reader locks allowed
298+ private int m_maximumRetryAttempts; // Maximum retry attempts allowed for loading file
299+ private BitArray m_retryQueue; // Retry event queue
300+ private System.Timers.Timer m_retryTimer; // File I/O retry timer
301+ private long m_lastRetryTime; // Time of last retry attempt
302+ private int m_retryCount; // Total number of retries attempted so far
303+ private bool m_disposed; // Class disposed flag
304+
305+ #endregion
306+
307+ #region [ Constructors ]
308+
309+ /// <summary>
310+ /// Creates a new instance of the <see cref="InterprocessCache"/>.
311+ /// </summary>
312+ public InterprocessCache()
313+ : this(InterprocessReaderWriterLock.DefaultMaximumConcurrentLocks)
314+ {
315+ }
316+
317+ /// <summary>
318+ /// Creates a new instance of the <see cref="InterprocessCache"/> with the specified number of <paramref name="maximumConcurrentLocks"/>.
319+ /// </summary>
320+ /// <param name="maximumConcurrentLocks">Maximum concurrent reader locks to allow.</param>
321+ public InterprocessCache(int maximumConcurrentLocks)
322+ {
323+ // Initialize field values
324+ m_dataLock = new ReaderWriterLockSlim();
325+ m_loadIsReady = new ManualResetEventSlim(false);
326+ m_saveIsReady = new ManualResetEventSlim(true);
327+ m_maximumConcurrentLocks = maximumConcurrentLocks;
328+ m_maximumRetryAttempts = DefaultMaximumRetryAttempts;
329+ m_retryQueue = new BitArray(2);
330+ m_fileData = new byte[0];
331+
332+ // Setup retry timer
333+ m_retryTimer = new System.Timers.Timer();
334+ m_retryTimer.Elapsed += m_retryTimer_Elapsed;
335+ m_retryTimer.AutoReset = false;
336+ m_retryTimer.Interval = DefaultRetryDelayInterval;
337+ }
338+
339+ /// <summary>
340+ /// Releases the unmanaged resources before the <see cref="InterprocessCache"/> object is reclaimed by <see cref="GC"/>.
341+ /// </summary>
342+ ~InterprocessCache()
343+ {
344+ Dispose(false);
345+ }
346+
347+ #endregion
348+
349+ #region [ Properties ]
350+
351+ /// <summary>
352+ /// Path and file name for the cache needing interprocess synchronization.
353+ /// </summary>
354+ public virtual string FileName
355+ {
356+ get
357+ {
358+ return m_fileName;
359+ }
360+ set
361+ {
362+ if ((object)value == null)
363+ throw new ArgumentNullException("FileName", "FileName cannot be null");
364+
365+ m_fileName = FilePath.GetAbsolutePath(value);
366+
367+ // Initialize reader/writer lock for given file name
368+ if ((object)m_fileLock != null)
369+ m_fileLock.Dispose();
370+
371+ m_fileLock = new InterprocessReaderWriterLock(m_fileName, m_maximumConcurrentLocks);
372+ }
373+ }
374+
375+ /// <summary>
376+ /// Gets or sets file data for the cache to be saved or that has been loaded.
377+ /// </summary>
378+ /// <remarks>
379+ /// Setting value to <c>null</c> will create a zero-length file.
380+ /// </remarks>
381+ public virtual byte[] FileData
382+ {
383+ get
384+ {
385+ // Calls to this property are blocked until data is available
386+ WaitForLoad();
387+
388+ m_dataLock.EnterReadLock();
389+
390+ try
391+ {
392+ // Make a copy of the file data for external use
393+ if ((object)m_fileData != null)
394+ return m_fileData.Copy(0, m_fileData.Length);
395+ }
396+ finally
397+ {
398+ m_dataLock.ExitReadLock();
399+ }
400+
401+ return null;
402+ }
403+ set
404+ {
405+ if ((object)m_fileName == null)
406+ throw new ArgumentNullException("FileName", "FileName property must be defined before setting FileData");
407+
408+ bool dataChanged = false;
409+
410+ // If value is null, assume user means zero-length file
411+ if ((object)value == null)
412+ value = new byte[0];
413+
414+ m_dataLock.EnterWriteLock();
415+
416+ try
417+ {
418+ if (m_autoSave)
419+ dataChanged = (m_fileData.CompareTo(value) != 0);
420+
421+ m_fileData = value;
422+ }
423+ finally
424+ {
425+ m_dataLock.ExitWriteLock();
426+ }
427+
428+ // Initiate save if data has changed
429+ if (dataChanged)
430+ {
431+ m_saveIsReady.Reset();
432+ ThreadPool.QueueUserWorkItem(SynchronizedWrite);
433+ }
434+ }
435+ }
436+
437+ /// <summary>
438+ /// Gets or sets flag that determines if <see cref="InterprocessCache"/> should automatically initiate a save when <see cref="FileData"/> has been updated.
439+ /// </summary>
440+ public virtual bool AutoSave
441+ {
442+ get
443+ {
444+ return m_autoSave;
445+ }
446+ set
447+ {
448+ m_autoSave = value;
449+ }
450+ }
451+
452+ /// <summary>
453+ /// Gets or sets flag that enables system to monitor for changes in <see cref="FileName"/> and automatically reload <see cref="FileData"/>.
454+ /// </summary>
455+ public virtual bool ReloadOnChange
456+ {
457+ get
458+ {
459+ return (object)m_fileWatcher != null;
460+ }
461+ set
462+ {
463+ if (value && (object)m_fileWatcher == null)
464+ {
465+ if ((object)m_fileName == null)
466+ throw new ArgumentNullException("FileName", "FileName property must be defined before enabling ReloadOnChange");
467+
468+ // Setup file watcher to monitor for external updates
469+ m_fileWatcher = new FileSystemWatcher();
470+ m_fileWatcher.Path = FilePath.GetDirectoryName(m_fileName);
471+ m_fileWatcher.Filter = FilePath.GetFileName(m_fileName);
472+ m_fileWatcher.EnableRaisingEvents = true;
473+ m_fileWatcher.Changed += m_fileWatcher_Changed;
474+ }
475+ else if (!value && (object)m_fileWatcher != null)
476+ {
477+ // Disable file watcher
478+ m_fileWatcher.Changed -= m_fileWatcher_Changed;
479+ m_fileWatcher.Dispose();
480+ m_fileWatcher = null;
481+ }
482+ }
483+ }
484+
485+ /// <summary>
486+ /// Gets the maximum concurrent reader locks allowed.
487+ /// </summary>
488+ public virtual int MaximumConcurrentLocks
489+ {
490+ get
491+ {
492+ return m_maximumConcurrentLocks;
493+ }
494+ }
495+
496+ /// <summary>
497+ /// Maximum retry attempts allowed for loading or saving cache file data.
498+ /// </summary>
499+ public virtual int MaximumRetryAttempts
500+ {
501+ get
502+ {
503+ return m_maximumRetryAttempts;
504+ }
505+ set
506+ {
507+ m_maximumRetryAttempts = value;
508+ }
509+ }
510+
511+ /// <summary>
512+ /// Wait interval, in milliseconds, before retrying load or save of cache file data.
513+ /// </summary>
514+ public virtual double RetryDelayInterval
515+ {
516+ get
517+ {
518+ return m_retryTimer.Interval;
519+ }
520+ set
521+ {
522+ m_retryTimer.Interval = value;
523+ }
524+ }
525+
526+ #endregion
527+
528+ #region [ Methods ]
529+
530+ /// <summary>
531+ /// Releases all the resources used by the <see cref="InterprocessCache"/> object.
532+ /// </summary>
533+ public void Dispose()
534+ {
535+ Dispose(true);
536+ GC.SuppressFinalize(this);
537+ }
538+
539+ /// <summary>
540+ /// Releases the unmanaged resources used by the <see cref="InterprocessCache"/> object and optionally releases the managed resources.
541+ /// </summary>
542+ /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
543+ protected virtual void Dispose(bool disposing)
544+ {
545+ if (!m_disposed)
546+ {
547+ try
548+ {
549+ if (disposing)
550+ {
551+ if ((object)m_fileWatcher != null)
552+ {
553+ m_fileWatcher.Changed -= m_fileWatcher_Changed;
554+ m_fileWatcher.Dispose();
555+ }
556+ m_fileWatcher = null;
557+
558+ if ((object)m_retryTimer != null)
559+ {
560+ m_retryTimer.Elapsed -= m_retryTimer_Elapsed;
561+ m_retryTimer.Dispose();
562+ }
563+ m_retryTimer = null;
564+
565+ if ((object)m_loadIsReady != null)
566+ m_loadIsReady.Dispose();
567+
568+ m_loadIsReady = null;
569+
570+ if ((object)m_saveIsReady != null)
571+ m_saveIsReady.Dispose();
572+
573+ m_saveIsReady = null;
574+
575+ if ((object)m_dataLock != null)
576+ m_dataLock.Dispose();
577+
578+ m_dataLock = null;
579+
580+ if ((object)m_fileLock != null)
581+ m_fileLock.Dispose();
582+
583+ m_fileLock = null;
584+ m_fileName = null;
585+ }
586+ }
587+ finally
588+ {
589+ m_disposed = true; // Prevent duplicate dispose.
590+ }
591+ }
592+ }
593+
594+ /// <summary>
595+ /// Initiates interprocess synchronized cache file save.
596+ /// </summary>
597+ /// <remarks>
598+ /// Subclasses should always call <see cref="WaitForLoad()"/> before calling this method.
599+ /// </remarks>
600+ public virtual void Save()
601+ {
602+ if ((object)m_fileName == null)
603+ throw new ArgumentNullException("FileName", "FileName is null, cannot initiate save");
604+
605+ if ((object)m_fileData == null)
606+ throw new ArgumentNullException("FileData", "FileData is null, cannot initiate save");
607+
608+ m_saveIsReady.Reset();
609+ ThreadPool.QueueUserWorkItem(SynchronizedWrite);
610+ }
611+
612+ /// <summary>
613+ /// Initiates interprocess synchronized cache file load.
614+ /// </summary>
615+ /// <remarks>
616+ /// Subclasses should always call <see cref="WaitForLoad()"/> before calling this method.
617+ /// </remarks>
618+ public virtual void Load()
619+ {
620+ if ((object)m_fileName == null)
621+ throw new ArgumentNullException("FileName", "FileName is null, cannot initiate load");
622+
623+ m_loadIsReady.Reset();
624+ ThreadPool.QueueUserWorkItem(SynchronizedRead);
625+ }
626+
627+ /// <summary>
628+ /// Blocks current thread and waits for any pending load to complete; wait time is <c><see cref="RetryDelayInterval"/> * <see cref="MaximumRetryAttempts"/></c>.
629+ /// </summary>
630+ public virtual void WaitForLoad()
631+ {
632+ WaitForLoad((int)(RetryDelayInterval * MaximumRetryAttempts));
633+ }
634+
635+ /// <summary>
636+ /// Blocks current thread and waits for specified <paramref name="millisecondsTimeout"/> for any pending load to complete.
637+ /// </summary>
638+ /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
639+ public virtual void WaitForLoad(int millisecondsTimeout)
640+ {
641+ // Calls to this method are blocked until data is available
642+ if (!m_loadIsReady.IsSet && !m_loadIsReady.Wait(millisecondsTimeout))
643+ throw new TimeoutException("Timeout waiting to read data from " + m_fileName);
644+ }
645+
646+ /// <summary>
647+ /// Blocks current thread and waits for any pending save to complete; wait time is <c><see cref="RetryDelayInterval"/> * <see cref="MaximumRetryAttempts"/></c>.
648+ /// </summary>
649+ public virtual void WaitForSave()
650+ {
651+ WaitForSave((int)(RetryDelayInterval * MaximumRetryAttempts));
652+ }
653+
654+ /// <summary>
655+ /// Blocks current thread and waits for specified <paramref name="millisecondsTimeout"/> for any pending save to complete.
656+ /// </summary>
657+ /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
658+ public virtual void WaitForSave(int millisecondsTimeout)
659+ {
660+ // Calls to this method are blocked until data is saved
661+ if (!m_saveIsReady.IsSet && !m_saveIsReady.Wait(millisecondsTimeout))
662+ throw new TimeoutException("Timeout waiting to save data to " + m_fileName);
663+ }
664+
665+ /// <summary>
666+ /// Handles serialization of file to disk; virtual method allows customization (e.g., pre-save encryption and/or data merge).
667+ /// </summary>
668+ /// <param name="fileStream"><see cref="FileStream"/> used to serialize data.</param>
669+ /// <param name="fileData">File data to be serialized.</param>
670+ /// <remarks>
671+ /// Consumers overriding this method should not directly call <see cref="FileData"/> property to avoid potential dead-locks.
672+ /// </remarks>
673+ protected virtual void SaveFileData(FileStream fileStream, byte[] fileData)
674+ {
675+ fileStream.Write(fileData, 0, fileData.Length);
676+ }
677+
678+ /// <summary>
679+ /// Handles deserialization of file from disk; virtual method allows customization (e.g., pre-load decryption and/or data merge).
680+ /// </summary>
681+ /// <param name="fileStream"><see cref="FileStream"/> used to deserialize data.</param>
682+ /// <returns>Deserialized file data.</returns>
683+ /// <remarks>
684+ /// Consumers overriding this method should not directly call <see cref="FileData"/> property to avoid potential dead-locks.
685+ /// </remarks>
686+ protected virtual byte[] LoadFileData(FileStream fileStream)
687+ {
688+ return fileStream.ReadStream();
689+ }
690+
691+ /// <summary>
692+ /// Synchronously writes file data when no reads are active.
693+ /// </summary>
694+ private void SynchronizedWrite(object state)
695+ {
696+ try
697+ {
698+ if (!m_disposed)
699+ {
700+ if (m_fileLock.TryEnterWriteLock((int)m_retryTimer.Interval))
701+ {
702+ FileStream fileStream = null;
703+
704+ try
705+ {
706+ fileStream = new FileStream(m_fileName, FileMode.Create, FileAccess.Write, FileShare.None);
707+
708+ if (m_dataLock.TryEnterReadLock((int)m_retryTimer.Interval))
709+ {
710+ try
711+ {
712+ // Disable file watch notification before update
713+ if ((object)m_fileWatcher != null)
714+ m_fileWatcher.EnableRaisingEvents = false;
715+
716+ SaveFileData(fileStream, m_fileData);
717+ m_saveIsReady.Set();
718+ }
719+ finally
720+ {
721+ m_dataLock.ExitReadLock();
722+
723+ // Reenable file watch notification
724+ if ((object)m_fileWatcher != null)
725+ m_fileWatcher.EnableRaisingEvents = true;
726+ }
727+ }
728+ else
729+ {
730+ RetrySynchronizedEvent(new TimeoutException("Timeout waiting to acquire read lock for local cache"), WriteEvent);
731+ }
732+ }
733+ catch (IOException ex)
734+ {
735+ RetrySynchronizedEvent(ex, WriteEvent);
736+ }
737+ finally
738+ {
739+ m_fileLock.ExitWriteLock();
740+
741+ if ((object)fileStream != null)
742+ fileStream.Close();
743+ }
744+ }
745+ else
746+ {
747+ RetrySynchronizedEvent(new TimeoutException("Timeout waiting to acquire write lock for " + m_fileName), WriteEvent);
748+ }
749+ }
750+ }
751+ catch (ThreadAbortException)
752+ {
753+ // This is an normal exception
754+ throw;
755+ }
756+ catch
757+ {
758+ // Other exceptions can happen (e.g., NullReferenceException) if thread resumes and the class is disposed middle way through this method
759+ }
760+ }
761+
762+ /// <summary>
763+ /// Synchronously reads file data when no writes are active.
764+ /// </summary>
765+ private void SynchronizedRead(object state)
766+ {
767+ try
768+ {
769+ if (!m_disposed)
770+ {
771+ if (File.Exists(m_fileName))
772+ {
773+ if (m_fileLock.TryEnterReadLock((int)m_retryTimer.Interval))
774+ {
775+ FileStream fileStream = null;
776+
777+ try
778+ {
779+ fileStream = new FileStream(m_fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
780+
781+ if (m_dataLock.TryEnterWriteLock((int)m_retryTimer.Interval))
782+ {
783+ try
784+ {
785+ m_fileData = LoadFileData(fileStream);
786+ }
787+ finally
788+ {
789+ m_dataLock.ExitWriteLock();
790+ }
791+
792+ // Release any threads waiting for file data
793+ m_loadIsReady.Set();
794+ }
795+ else
796+ {
797+ RetrySynchronizedEvent(new TimeoutException("Timeout waiting to acquire write lock for local cache"), ReadEvent);
798+ }
799+ }
800+ catch (IOException ex)
801+ {
802+ RetrySynchronizedEvent(ex, ReadEvent);
803+ }
804+ finally
805+ {
806+ m_fileLock.ExitReadLock();
807+
808+ if ((object)fileStream != null)
809+ fileStream.Close();
810+ }
811+ }
812+ else
813+ {
814+ RetrySynchronizedEvent(new TimeoutException("Timeout waiting to acquire read lock for " + m_fileName), ReadEvent);
815+ }
816+ }
817+ else
818+ {
819+ // File doesn't exist, create ane empty array representing a zero-length file
820+ m_fileData = new byte[0];
821+
822+ // Release any threads waiting for file data
823+ m_loadIsReady.Set();
824+ }
825+ }
826+ }
827+ catch (ThreadAbortException)
828+ {
829+ // This is an normal exception
830+ throw;
831+ }
832+ catch
833+ {
834+ // Other exceptions can happen (e.g., NullReferenceException) if thread resumes and the class is disposed middle way through this method
835+ }
836+ }
837+
838+ /// <summary>
839+ /// Initiates a retry for specified event type.
840+ /// </summary>
841+ /// <param name="ex">Exception causing retry.</param>
842+ /// <param name="eventType">Event type to retry.</param>
843+ private void RetrySynchronizedEvent(Exception ex, int eventType)
844+ {
845+ // A retry is only being initiating for basic file I/O or locking errors, all other errors will initiate an unhandled
846+ // exception causing system exit. It would be an error, IMO, for the system to create values then not be able to load
847+ // them at next run or not be able to use values from last run because file could not be loaded.
848+ if (!m_disposed)
849+ {
850+ // We monitor basic I/O and lock failures occurring in quick succession, we can't allow retry activity to go on forever
851+ if (DateTime.UtcNow.Ticks - m_lastRetryTime > (long)Ticks.FromMilliseconds(m_retryTimer.Interval * m_maximumRetryAttempts))
852+ {
853+ // Significant time has passed since last retry, so we reset counter
854+ m_retryCount = 0;
855+ m_lastRetryTime = DateTime.UtcNow.Ticks;
856+ }
857+ else
858+ {
859+ m_retryCount++;
860+
861+ if (m_retryCount >= m_maximumRetryAttempts)
862+ throw new UnauthorizedAccessException("Failed to " + (eventType == WriteEvent ? "write data to " : "read data from ") + m_fileName + " after " + m_maximumRetryAttempts + " attempts: " + ex.Message, ex);
863+ }
864+
865+ // Technically the interprocess mutex will handle serialized access to the file, but if the OS or other process
866+ // not participating with the mutex has the file locked, all we can do is queue up a retry for this event.
867+ lock (m_retryQueue)
868+ {
869+ m_retryQueue[eventType] = true;
870+ }
871+ m_retryTimer.Start();
872+ }
873+ }
874+
875+ /// <summary>
876+ /// Retries specified serialize or deserialize event in case of file I/O failures.
877+ /// </summary>
878+ private void m_retryTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
879+ {
880+ if (!m_disposed)
881+ {
882+ WaitCallback callBackEvent = null;
883+
884+ lock (m_retryQueue)
885+ {
886+ // Reads should always occur first since you may need to load any
887+ // newly written data before saving new data. Users can override
888+ // load and save behavior to "merge" data sets if needed.
889+ if (m_retryQueue[ReadEvent])
890+ {
891+ callBackEvent = SynchronizedRead;
892+ m_retryQueue[ReadEvent] = false;
893+ }
894+ else if (m_retryQueue[WriteEvent])
895+ {
896+ callBackEvent = SynchronizedWrite;
897+ m_retryQueue[WriteEvent] = false;
898+ }
899+
900+ // If any events remain queued for retry, start timer for next event
901+ if (m_retryQueue.Any(true))
902+ m_retryTimer.Start();
903+ }
904+
905+ if ((object)callBackEvent != null)
906+ ThreadPool.QueueUserWorkItem(callBackEvent);
907+ }
908+ }
909+
910+ /// <summary>
911+ /// Reload file upon external modification.
912+ /// </summary>
913+ /// <param name="sender">The object that triggered the event.</param>
914+ /// <param name="e">An object which provides data for directory events.</param>
915+ private void m_fileWatcher_Changed(object sender, FileSystemEventArgs e)
916+ {
917+ if (e.ChangeType == WatcherChangeTypes.Changed)
918+ Load();
919+ }
920+
921+ #endregion
922+ }
923+}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t