public static async Task CreateCacheFileAsync( HttpCacheResult result, string uri, HttpResponseMessage response, HttpSourceCacheContext context, Action <Stream> ensureValidContents, CancellationToken cancellationToken) { // The update of a cached file is divided into two steps: // 1) Delete the old file. // 2) Create a new file with the same name. using (var fileStream = new FileStream( result.NewCacheFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None, BufferSize, useAsync: true)) { using (var networkStream = await response.Content.ReadAsStreamAsync()) { await networkStream.CopyToAsync(fileStream, BufferSize, cancellationToken); } // Validate the content before putting it into the cache. fileStream.Seek(0, SeekOrigin.Begin); ensureValidContents?.Invoke(fileStream); } if (File.Exists(result.CacheFile)) { // Process B can perform deletion on an opened file if the file is opened by process A // with FileShare.Delete flag. However, the file won't be actually deleted until A close it. // This special feature can cause race condition, so we never delete an opened file. if (!IsFileAlreadyOpen(result.CacheFile)) { File.Delete(result.CacheFile); } } // If the destination file doesn't exist, we can safely perform moving operation. // Otherwise, moving operation will fail. if (!File.Exists(result.CacheFile)) { File.Move( result.NewCacheFile, result.CacheFile); } // Even the file deletion operation above succeeds but the file is not actually deleted, // we can still safely read it because it means that some other process just updated it // and we don't need to update it with the same content again. result.Stream = new FileStream( result.CacheFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, BufferSize, useAsync: true); }
public static async Task CreateCacheFileAsync( HttpCacheResult result, HttpResponseMessage response, Action <Stream> ensureValidContents, CancellationToken cancellationToken) { // Get the cache file directories, so we can make sure they are created before writing // files to them. var newCacheFileDirectory = Path.GetDirectoryName(result.NewFile); var cacheFileDirectory = Path.GetDirectoryName(result.CacheFile); // Make sure the new cache file directory is created before writing a file to it. DirectoryUtility.CreateSharedDirectory(newCacheFileDirectory); // The update of a cached file is divided into two steps: // 1) Delete the old file. // 2) Create a new file with the same name. using (var fileStream = new FileStream( result.NewFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None, BufferSize)) { using (var networkStream = await response.Content.ReadAsStreamAsync()) { await networkStream.CopyToAsync(fileStream, BufferSize, cancellationToken); } // Validate the content before putting it into the cache. if (ensureValidContents != null) { fileStream.Seek(0, SeekOrigin.Begin); ensureValidContents.Invoke(fileStream); } } if (File.Exists(result.CacheFile)) { // Process B can perform deletion on an opened file if the file is opened by process A // with FileShare.Delete flag. However, the file won't be actually deleted until A close it. // This special feature can cause race condition, so we never delete an opened file. if (!CachingUtility.IsFileAlreadyOpen(result.CacheFile)) { File.Delete(result.CacheFile); } } // Make sure the cache file directory is created before moving or writing a file to it. if (cacheFileDirectory != newCacheFileDirectory) { DirectoryUtility.CreateSharedDirectory(cacheFileDirectory); } // If the destination file doesn't exist, we can safely perform moving operation. // Otherwise, moving operation will fail. if (!File.Exists(result.CacheFile)) { File.Move( result.NewFile, result.CacheFile); } // Even the file deletion operation above succeeds but the file is not actually deleted, // we can still safely read it because it means that some other process just updated it // and we don't need to update it with the same content again. result.Stream = new FileStream( result.CacheFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, BufferSize); }
public static async Task CreateCacheFileAsync( HttpCacheResult result, HttpResponseMessage response, Action <Stream> ensureValidContents, CancellationToken cancellationToken) { // Get the cache file directories, so we can make sure they are created before writing // files to them. var newCacheFileDirectory = Path.GetDirectoryName(result.NewFile); var cacheFileDirectory = Path.GetDirectoryName(result.CacheFile); // Make sure the new cache file directory is created before writing a file to it. DirectoryUtility.CreateSharedDirectory(newCacheFileDirectory); // The update of a cached file is divided into two steps: // 1) Delete the old file. // 2) Create a new file with the same name. // Some FileStream operations on Windows are synchronous even though it may not seem so. // The HTTP stack rewrite in .NET Core 2.1 introduced circumstances whereby these // synchronous FileStream calls will keep an IO completion thread busy and block other // HTTP requests from completing. The immediate solution is to perform write and read // operations on separate streams, but only on .NET Core where the problem exists. // See https://github.com/dotnet/corefx/issues/31914 for details. const int writeBufferSize = #if IS_CORECLR 1; // This disables write buffering. #else BufferSize; #endif using (var fileStream = new FileStream( result.NewFile, FileMode.Create, FileAccess.Write, FileShare.None, writeBufferSize, useAsync: true)) { using (var networkStream = await response.Content.ReadAsStreamAsync()) { await networkStream.CopyToAsync(fileStream, BufferSize, cancellationToken); } } using (var fileStream = new FileStream( result.NewFile, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, useAsync: true)) { // Validate the content before putting it into the cache. ensureValidContents?.Invoke(fileStream); } if (File.Exists(result.CacheFile)) { // Process B can perform deletion on an opened file if the file is opened by process A // with FileShare.Delete flag. However, the file won't be actually deleted until A close it. // This special feature can cause race condition, so we never delete an opened file. if (!CachingUtility.IsFileAlreadyOpen(result.CacheFile)) { File.Delete(result.CacheFile); } } // Make sure the cache file directory is created before moving or writing a file to it. if (cacheFileDirectory != newCacheFileDirectory) { DirectoryUtility.CreateSharedDirectory(cacheFileDirectory); } // If the destination file doesn't exist, we can safely perform moving operation. // Otherwise, moving operation will fail. if (!File.Exists(result.CacheFile)) { File.Move( result.NewFile, result.CacheFile); } // Even the file deletion operation above succeeds but the file is not actually deleted, // we can still safely read it because it means that some other process just updated it // and we don't need to update it with the same content again. result.Stream = new FileStream( result.CacheFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, BufferSize, useAsync: true); }