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
254using GSF.Collections;
255using GSF.Threading;
256using System;
257using System.Collections;
258using System.IO;
259using System.Threading;
260
261namespace 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