internal override void Delete(StorageTransmission item) { try { if (StorageFolder == null) { return; } // Initial storage size calculation. CalculateSize(); long fileSize = GetSize(item.FileName); File.Delete(Path.Combine(StorageFolder, item.FileName)); _deletedFilesQueue.Enqueue(item.FileName); // calculate size Interlocked.Add(ref _storageSize, -fileSize); Interlocked.Decrement(ref _storageCountFiles); } catch (IOException e) { PersistenceChannelDebugLog.WriteException(e, "Failed to delete a file. file: {0}", item == null ? "null" : item.FullFilePath); } }
protected void OnPeekedItemDisposed(string fileName) { try { if (PeekedTransmissions.ContainsKey(fileName)) { PeekedTransmissions.Remove(fileName); } } catch (Exception e) { PersistenceChannelDebugLog.WriteException(e, "Failed to remove the item from storage items."); } }
internal override async Task EnqueueAsync(Transmission transmission) { try { if (transmission == null || StorageFolder == null) { return; } // Initial storage size calculation. CalculateSize(); if ((ulong)_storageSize >= CapacityInBytes || _storageCountFiles >= MaxFiles) { // if max storage capacity has reached, drop the transmission (but log every 100 lost transmissions). if (_transmissionsDropped++ % 100 == 0) { PersistenceChannelDebugLog.WriteLine("Total transmissions dropped: " + _transmissionsDropped); } return; } // Writes content to a temporary file and only then rename to avoid the Peek from reading the file before it is being written. // Creates the temp file name string tempFileName = Guid.NewGuid().ToString("N"); // Now that the file got created we can increase the files count Interlocked.Increment(ref _storageCountFiles); // Saves transmission to the temp file await SaveTransmissionToFileAsync(transmission, tempFileName).ConfigureAwait(false); // Now that the file is written increase storage size. long temporaryFileSize = GetSize(tempFileName); Interlocked.Add(ref _storageSize, temporaryFileSize); // Creates a new file name string now = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); string newFileName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}.trn", now, tempFileName); // Renames the file File.Move(Path.Combine(StorageFolder, tempFileName), Path.Combine(StorageFolder, newFileName)); } catch (Exception e) { PersistenceChannelDebugLog.WriteException(e, "EnqueueAsync"); } }
/// <summary> /// Get files from <see cref="storageFolder" />. /// </summary> /// <param name="fileQuery">Define the logic for sorting the files.</param> /// <param name="filterByExtension">Defines a file extension. This method will return only files with this extension.</param> /// <param name="top"> /// Define how many files to return. This can be useful when the directory has a lot of files, in that case /// GetFilesAsync will have a performance hit. /// </param> private IEnumerable <string> GetFiles(string filterByExtension, int top) { try { if (StorageFolder != null) { return(Directory.GetFiles(StorageFolder, filterByExtension).Take(top)); } } catch (Exception e) { PersistenceChannelDebugLog.WriteException(e, "Peek failed while get files from storage."); } return(Enumerable.Empty <string>()); }
internal override void Init(string storageDirectoryPath) { PeekedTransmissions = new SnapshottingDictionary <string, string>(); VerifyOrSetDefaultStorageDirectoryPath(storageDirectoryPath); CapacityInBytes = 10 * 1024 * 1024; // 10 MB MaxFiles = 100; Task.Run(DeleteObsoleteFiles) .ContinueWith( task => { PersistenceChannelDebugLog.WriteException( task.Exception, "Storage: Unhandled exception in DeleteObsoleteFiles"); }, TaskContinuationOptions.OnlyOnFaulted); }
/// <summary> /// Enqueue is saving a transmission to a <c>tmp</c> file and after a successful write operation it renames it to a /// <c>trn</c> file. /// A file without a <c>trn</c> extension is ignored by Storage.Peek(), so if a process is taken down before rename /// happens /// it will stay on the disk forever. /// This thread deletes files with the <c>tmp</c> extension that exists on disk for more than 5 minutes. /// </summary> private void DeleteObsoleteFiles() { try { IEnumerable <string> files = GetFiles("*.tmp", 50); foreach (string file in files) { DateTime creationTime = File.GetCreationTimeUtc(Path.Combine(StorageFolder, file)); // if the file is older then 5 minutes - delete it. if (DateTime.UtcNow - creationTime >= TimeSpan.FromMinutes(5)) { File.Delete(Path.Combine(StorageFolder, file)); } } } catch (Exception e) { PersistenceChannelDebugLog.WriteException(e, "Failed to delete tmp files."); } }
/// <summary> /// Reads an item from the storage. Order is Last-In-First-Out. /// When the Transmission is no longer needed (it was either sent or failed with a non-retryable error) it should be /// disposed. /// </summary> internal override StorageTransmission Peek() { IEnumerable <string> files = GetFiles("*.trn", 50); lock (_peekLockObj) { foreach (string file in files) { try { // if a file was peeked before, skip it (wait until it is disposed). if (PeekedTransmissions.ContainsKey(file) == false && _deletedFilesQueue.Contains(file) == false) { // Load the transmission from disk. StorageTransmission storageTransmissionItem = LoadTransmissionFromFileAsync(file) .ConfigureAwait(false).GetAwaiter().GetResult(); // when item is disposed it should be removed from the peeked list. storageTransmissionItem.Disposing = item => OnPeekedItemDisposed(file); // add the transmission to the list. PeekedTransmissions.Add(file, storageTransmissionItem.FullFilePath); return(storageTransmissionItem); } } catch (Exception e) { PersistenceChannelDebugLog.WriteException( e, "Failed to load an item from the storage. file: {0}", file); } } } return(null); }
/// <summary> /// Initializes a new instance of the <see cref="Sender" /> class. /// </summary> /// <param name="storage">The storage that holds the transmissions to send.</param> /// <param name="transmitter"> /// The persistence transmitter that manages this Sender. /// The transmitter will be used as a configuration class, it exposes properties like SendingInterval that will be read /// by Sender. /// </param> /// <param name="startSending"> /// A boolean value that determines if Sender should start sending immediately. This is only /// used for unit tests. /// </param> internal Sender(BaseStorageService storage, PersistenceTransmitter transmitter, bool startSending = true) { _stopped = false; DelayHandler = new AutoResetEvent(false); _stoppedHandler = new AutoResetEvent(false); _drainingTimeout = TimeSpan.FromSeconds(100); _defaultSendingInterval = TimeSpan.FromSeconds(5); _transmitter = transmitter; _storage = storage; if (startSending) { // It is currently possible for the long - running task to be executed(and thereby block during WaitOne) on the UI thread when // called by a task scheduled on the UI thread. Explicitly specifying TaskScheduler.Default // when calling StartNew guarantees that Sender never blocks the main thread. Task.Factory.StartNew(SendLoop, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default) .ContinueWith( t => PersistenceChannelDebugLog.WriteException(t.Exception, "Sender: Failure in SendLoop"), TaskContinuationOptions.OnlyOnFaulted); } }
/// <summary> /// Sends a transmission and handle errors. /// </summary> /// <param name="transmission">The transmission to send.</param> /// <param name="nextSendInterval"> /// When this value returns it will hold a recommendation for when to start the next sending /// iteration. /// </param> /// <returns>True, if there was sent error and we need to retry sending, otherwise false.</returns> protected virtual bool Send(StorageTransmission transmission, ref TimeSpan nextSendInterval) { try { if (transmission != null) { bool isConnected = NetworkInterface.GetIsNetworkAvailable(); // there is no internet connection available, return than. if (!isConnected) { PersistenceChannelDebugLog.WriteLine( "Cannot send data to the server. Internet connection is not available"); return(true); } transmission.SendAsync().ConfigureAwait(false).GetAwaiter().GetResult(); // After a successful sending, try immediately to send another transmission. nextSendInterval = SendingInterval; } } catch (WebException e) { int?statusCode = GetStatusCode(e); nextSendInterval = CalculateNextInterval(statusCode, nextSendInterval, _maxIntervalBetweenRetries); return(IsRetryable(statusCode, e.Status)); } catch (Exception e) { nextSendInterval = CalculateNextInterval(null, nextSendInterval, _maxIntervalBetweenRetries); PersistenceChannelDebugLog.WriteException(e, "Unknown exception during sending"); } return(false); }