@@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.NodeServices
99 public sealed class StringAsTempFile : IDisposable
1010 {
1111 private bool _disposedValue ;
12+ private bool _hasDeletedTempFile ;
13+ private object _fileDeletionLock = new object ( ) ;
1214
1315 /// <summary>
1416 /// Create a new instance of <see cref="StringAsTempFile"/>.
@@ -18,6 +20,18 @@ public StringAsTempFile(string content)
1820 {
1921 FileName = Path . GetTempFileName ( ) ;
2022 File . WriteAllText ( FileName , content ) ;
23+
24+ // Because .NET finalizers don't reliably run when the process is terminating, also
25+ // add event handlers for other shutdown scenarios.
26+ #if NET451
27+ AppDomain . CurrentDomain . ProcessExit += HandleProcessExit ;
28+ AppDomain . CurrentDomain . DomainUnload += HandleProcessExit ;
29+ #else
30+ // Note that this still doesn't capture SIGKILL (at least on macOS) - there doesn't
31+ // appear to be a way of doing that. So in that case, the temporary file will be
32+ // left behind.
33+ System . Runtime . Loader . AssemblyLoadContext . Default . Unloading += HandleAssemblyUnloading ;
34+ #endif
2135 }
2236
2337 /// <summary>
@@ -40,15 +54,45 @@ private void DisposeImpl(bool disposing)
4054 {
4155 if ( disposing )
4256 {
43- // Would dispose managed state here, if there was any
57+ // Dispose managed state
58+ #if NET451
59+ AppDomain . CurrentDomain . ProcessExit -= HandleProcessExit ;
60+ AppDomain . CurrentDomain . DomainUnload -= HandleProcessExit ;
61+ #else
62+ System . Runtime . Loader . AssemblyLoadContext . Default . Unloading -= HandleAssemblyUnloading ;
63+ #endif
4464 }
4565
46- File . Delete ( FileName ) ;
66+ EnsureTempFileDeleted ( ) ;
4767
4868 _disposedValue = true ;
4969 }
5070 }
5171
72+ private void EnsureTempFileDeleted ( )
73+ {
74+ lock ( _fileDeletionLock )
75+ {
76+ if ( ! _hasDeletedTempFile )
77+ {
78+ File . Delete ( FileName ) ;
79+ _hasDeletedTempFile = true ;
80+ }
81+ }
82+ }
83+
84+ #if NET451
85+ private void HandleProcessExit ( object sender , EventArgs args )
86+ {
87+ EnsureTempFileDeleted ( ) ;
88+ }
89+ #else
90+ private void HandleAssemblyUnloading ( System . Runtime . Loader . AssemblyLoadContext context )
91+ {
92+ EnsureTempFileDeleted ( ) ;
93+ }
94+ #endif
95+
5296 /// <summary>
5397 /// Implements the finalization part of the IDisposable pattern by calling Dispose(false).
5498 /// </summary>
0 commit comments