From d7d2a2cce46a5de59410a5ebe5776a545f25a448 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 18 Oct 2024 11:21:04 +0100 Subject: [PATCH 1/4] Creating .NET MAUI Project sometimes causes error *.png is being used by another process Fixes https://github.com/dotnet/maui/issues/25207 Context https://github.com/dotnet/android-tools/pull/245 https://github.com/dotnet/android/pull/9409 So there is a problem where the design time builds (DTB) of android sometimes lock files. This can happen when two processes try to write to the same file. This is not a great experience for our users as it just fails the build. So lets try a few things to fix this. 1. Move the Resizetizer output for a DTB into the android `$(IntermediateOutputPath)designtime` folder. This will keep the DTB files completely separate. This should prevent clashes. 2. Add some retry code to the `SkiaSharpTools.Save` method. This will catch `UnauthorizedAccessException` exceptions as well as specific `IOException` types (Access Denied and Sharing Violation). This will allow us to catch when this happens and retry the write. There is a small delay before attempting to write the file again. Note these code uses the Identical code as we are going to use in Android. We have introduced two new environment variables which can be used to control the new behavior. 1. `DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS`. Integer, controls the number of times we try to write the file. The default is 10. 2. `DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS`. Integer, controls the delay in milliseconds between retry attempts. Default is 1000ms (or 1 second). --- .../Resizetizer/src/SkiaSharpTools.cs | 73 ++++++++++++++++++- .../Microsoft.Maui.Resizetizer.After.targets | 35 +++++---- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs b/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs index e81d311351c8..41562c4994a1 100644 --- a/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs +++ b/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs @@ -1,12 +1,54 @@ using System; using System.Diagnostics; using System.IO; +using System.Threading; +using System.Runtime.InteropServices; using SkiaSharp; namespace Microsoft.Maui.Resizetizer { internal abstract class SkiaSharpTools { + const int ERROR_ACCESS_DENIED = -2147024891; + const int ERROR_SHARING_VIOLATION = -2147024864; + const int DEFAULT_FILE_WRITE_RETRY_ATTEMPTS = 10; + const int DEFAULT_FILE_WRITE_RETRY_DELAY_MS = 1000; + + static int fileWriteRetry = -1; + static int fileWriteRetryDelay = -1; + + /// + /// Checks for the environment variable DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS to + /// see if a custom value for the number of times to retry writing a file has been + /// set. + /// + /// The value of DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS or the default of DEFAULT_FILE_WRITE_RETRY_ATTEMPTS + public static int GetFileWriteRetryAttempts () + { + if (fileWriteRetry == -1) { + var retryVariable = Environment.GetEnvironmentVariable ("DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS"); + if (string.IsNullOrEmpty (retryVariable) || !int.TryParse (retryVariable, out fileWriteRetry)) + fileWriteRetry = DEFAULT_FILE_WRITE_RETRY_ATTEMPTS; + } + return fileWriteRetry; + } + + /// + /// Checks for the environment variable DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS to + /// see if a custom value for the delay between trying to write a file has been + /// set. + /// + /// The value of DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS or the default of DEFAULT_FILE_WRITE_RETRY_DELAY_MS + public static int GetFileWriteRetryDelay () + { + if (fileWriteRetryDelay == -1) { + var delayVariable = Environment.GetEnvironmentVariable ("DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS"); + if (string.IsNullOrEmpty (delayVariable) || !int.TryParse (delayVariable, out fileWriteRetryDelay)) + fileWriteRetryDelay = DEFAULT_FILE_WRITE_RETRY_DELAY_MS; + } + return fileWriteRetryDelay; + } + static SkiaSharpTools() { // DO NOT DELETE! @@ -150,8 +192,35 @@ void Draw(SKBitmap tempBitmap, double additionalScale, SKSize originalSize, floa void Save(string destination, SKBitmap tempBitmap) { - using var stream = File.Create(destination); - tempBitmap.Encode(stream, SKEncodedImageFormat.Png, 100); + int attempt = 0; + int attempts = GetFileWriteRetryAttempts (); + int delay = GetFileWriteRetryDelay (); + while (attempt <= attempts) + { + try + { + using var stream = File.Create(destination); + tempBitmap.Encode(stream, SKEncodedImageFormat.Png, 100); + } + catch (Exception ex) + { + switch (ex) + { + case UnauthorizedAccessException: + case IOException: + var code = Marshal.GetHRForException(ex); + if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION) || attempt == attempts) + { + throw; + } + break; + default: + throw; + } + attempt++; + Thread.Sleep(delay); + } + } } public abstract SKSize GetOriginalSize(); diff --git a/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets b/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets index 0338df96324b..4a215b116acd 100644 --- a/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets +++ b/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets @@ -59,21 +59,6 @@ _CleanResizetizer; - <_ResizetizerInputsFile>$(IntermediateOutputPath)mauiimage.inputs - <_ResizetizerOutputsFile>$(IntermediateOutputPath)mauiimage.outputs - <_ResizetizerStampFile>$(IntermediateOutputPath)mauiimage.stamp - <_MauiFontInputsFile>$(IntermediateOutputPath)mauifont.inputs - <_MauiFontStampFile>$(IntermediateOutputPath)mauifont.stamp - <_MauiSplashInputsFile>$(IntermediateOutputPath)mauisplash.inputs - <_MauiSplashStampFile>$(IntermediateOutputPath)mauisplash.stamp - <_MauiManifestStampFile>$(IntermediateOutputPath)mauimanifest.stamp - - <_ResizetizerIntermediateOutputRoot>$(IntermediateOutputPath)resizetizer\ - <_MauiIntermediateImages>$(_ResizetizerIntermediateOutputRoot)r\ - <_MauiIntermediateFonts>$(_ResizetizerIntermediateOutputRoot)f\ - <_MauiIntermediateSplashScreen>$(_ResizetizerIntermediateOutputRoot)sp\ - <_MauiIntermediateManifest>$(_ResizetizerIntermediateOutputRoot)m\ - <_ResizetizerPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) <_ResizetizerNoTargetPlatform Condition="'$(_ResizetizerPlatformIdentifier)' == ''">True <_ResizetizerPlatformIsAndroid Condition="'$(_ResizetizerPlatformIdentifier)' == 'android'">True @@ -84,6 +69,24 @@ <_ResizetizerPlatformIsWindows Condition="$(_ResizetizerPlatformIdentifier.Contains('windows')) == 'True'">True <_ResizetizerPlatformIsTizen Condition="'$(_ResizetizerPlatformIdentifier)' == 'tizen'">True + <_ResizetizerIntermediateOutputPath Condition=" '$(_ResizetizerIntermediateOutputPath)' == '' And '$(_ResizetizerPlatformIsAndroid)' == 'True' And '$(DesignTimeBuild)' == 'True' ">$(IntermediateOutputPath)designtime + <_ResizetizerIntermediateOutputPath Condition=" '$(_ResizetizerIntermediateOutputPath)' == '' " >$(IntermediateOutputPath) + + <_ResizetizerInputsFile>$(_ResizetizerIntermediateOutputPath)mauiimage.inputs + <_ResizetizerOutputsFile>$(_ResizetizerIntermediateOutputPath)mauiimage.outputs + <_ResizetizerStampFile>$(_ResizetizerIntermediateOutputPath)mauiimage.stamp + <_MauiFontInputsFile>$(_ResizetizerIntermediateOutputPath)mauifont.inputs + <_MauiFontStampFile>$(_ResizetizerIntermediateOutputPath)mauifont.stamp + <_MauiSplashInputsFile>$(_ResizetizerIntermediateOutputPath)mauisplash.inputs + <_MauiSplashStampFile>$(_ResizetizerIntermediateOutputPath)mauisplash.stamp + <_MauiManifestStampFile>$(_ResizetizerIntermediateOutputPath)mauimanifest.stamp + + <_ResizetizerIntermediateOutputRoot>$(_ResizetizerIntermediateOutputPath)resizetizer\ + <_MauiIntermediateImages>$(_ResizetizerIntermediateOutputRoot)r\ + <_MauiIntermediateFonts>$(_ResizetizerIntermediateOutputRoot)f\ + <_MauiIntermediateSplashScreen>$(_ResizetizerIntermediateOutputRoot)sp\ + <_MauiIntermediateManifest>$(_ResizetizerIntermediateOutputRoot)m\ + False <_ResizetizerDefaultInvalidFilenamesErrorMessage>One or more invalid file names were detected. File names must be lowercase, start and end with a letter character, and contain only alphanumeric characters or underscores: @@ -504,7 +507,7 @@ - + From a4f701fd492c7c3c90f0aba4b219f0f85b0c3d7f Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 23 Oct 2024 15:41:11 +0100 Subject: [PATCH 2/4] Back out the change which moves the 'r' directory to designtime --- .../buildTransitive/Microsoft.Maui.Resizetizer.After.targets | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets b/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets index 4a215b116acd..feb26d3d5946 100644 --- a/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets +++ b/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets @@ -69,7 +69,6 @@ <_ResizetizerPlatformIsWindows Condition="$(_ResizetizerPlatformIdentifier.Contains('windows')) == 'True'">True <_ResizetizerPlatformIsTizen Condition="'$(_ResizetizerPlatformIdentifier)' == 'tizen'">True - <_ResizetizerIntermediateOutputPath Condition=" '$(_ResizetizerIntermediateOutputPath)' == '' And '$(_ResizetizerPlatformIsAndroid)' == 'True' And '$(DesignTimeBuild)' == 'True' ">$(IntermediateOutputPath)designtime <_ResizetizerIntermediateOutputPath Condition=" '$(_ResizetizerIntermediateOutputPath)' == '' " >$(IntermediateOutputPath) <_ResizetizerInputsFile>$(_ResizetizerIntermediateOutputPath)mauiimage.inputs From e728410fa99e29cfadbaa12244d1aeacf63d4b43 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 23 Oct 2024 16:30:07 +0100 Subject: [PATCH 3/4] Slight change to make sure we do not loop forever --- src/SingleProject/Resizetizer/src/SkiaSharpTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs b/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs index 41562c4994a1..f75775b3d947 100644 --- a/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs +++ b/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs @@ -209,7 +209,7 @@ void Save(string destination, SKBitmap tempBitmap) case UnauthorizedAccessException: case IOException: var code = Marshal.GetHRForException(ex); - if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION) || attempt == attempts) + if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION) || attempt >= attempts) { throw; } From f542c53cc88b5de2993f2933675694830748acf3 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 5 Dec 2024 17:10:58 +0000 Subject: [PATCH 4/4] Exit if all is well --- src/SingleProject/Resizetizer/src/SkiaSharpTools.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs b/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs index f75775b3d947..2ca629a7103e 100644 --- a/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs +++ b/src/SingleProject/Resizetizer/src/SkiaSharpTools.cs @@ -201,6 +201,7 @@ void Save(string destination, SKBitmap tempBitmap) { using var stream = File.Create(destination); tempBitmap.Encode(stream, SKEncodedImageFormat.Png, 100); + return; } catch (Exception ex) {