-
Notifications
You must be signed in to change notification settings - Fork 578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Template temporary dll deletion fails with System.UnauthorizedAccessException and files are left on disk #244
Comments
I cannot repro this. If you look into CompilationData (https://github.com/Antaris/RazorEngine/blob/master/src/source/RazorEngine.Core/Compilation/CompilationData.cs#L52) I cannot see how a Yes we have a problem here, we can't delete loaded dll files and there is nothing we can do, we try it anyway because we actually can cleanup on non-windows platforms. If you need proper cleanup in your application the only thing you can do is use Isolation and delete the files manually after shutting down the AppDomain (you need to communicate the folders manually). There is currently no API which helps you to implement this and you probably need your own For performance reasons it might even be better to create your own AppDomain instead of using the isolation API (so you can save the Serialization overhead) |
@matthid , first thanks for the quick response. Since the deadline for the project is close I was maybe too pushy to get some info, sorry for that. I made an incorrect formulation of the issue, you are right - the exceptions as such do not cause issues (I caught them because I had the debugging settings set to catch exceptions on being thrown, as you correctly stated, they are caught in Razor code). What I was mostly concerned about was the disk trashing since I had no way to keep track of the files created. Thanks for the explanation, I will look into making my own caching provider to clean up. Is there any sense in sticking with 3.3.0 instead of solving this? In the 3.3.0 code I used Compile().Run() each time and reading the documentation there should be a performance increase using precompiled templates and also I really like that I can catch parsing error on startup not on first use. Many thanks for the information. |
Yes in 3.3.0 |
Well, I'm mainly a C++ developer, my C# is not that good and after reading about AppDomain on MSDN, serialization etc., I don't think I can provide a nice solution, so I would be thankful if you can provide me with any hints on how to solve this in a trivial way and then I'll just gladly wait when such functionality is implemented inside Razor.
And, aside from the context of the issue, great engine - I love the possibility to deploy a rich web interface from a service:) |
All your observations are correct. I think even deleting all Razor_ stuff is quite safe because everything that is actually used is locked anyway (so you can't delete currently used files from other applications by chance). |
Thanks for the feedback:) Should I close this or leave it open as a feature request? |
I think we should leave it open as it certainly makes sense to think about it further and eventually fix this properly. |
+1. I don't yet have this issue, but was wondering about temp files ever since i upgraded from 3.4.x or something. As rubu mentioned above, having the ability to provide our own location where compiled assemblies are stored would be a big step forward. Having to poke around temp directories in file system is not really compelling, especially in production environment. Having a way to propely dispose of loaded code is a must. I've had a nasty memory leak with previous version. Afterall it was a bug on my side of code, but nevertheless having a way to just destroy all loaded compiled code would be awesome. I've had some experience with loading stuff into a separate AppDomain (unloadable plugins) so I'll try to take a look at this when I have some spare time on my hands. |
Yeah everything you need is there, making the path configurable should not be too complicated. If you can live with the performance impact you can use the already provided Isolation API. You do not even need to change the path, but instead implement a ICachingProvider which communicates all paths to your main AppDomain which then deletes everything once the Isolated AppDomain is unloaded. |
Maybe there is another way: We could change the default ICachingProvider in such a way that it spins up a new 'cleanup' AppDomain and communicates all paths to cleanup with it. After the main AppDomain has been unloaded the new 'cleanup' AppDomain jumps in and deletes the remaining files. This would probably slow down application shutdown times, but it would probably be acceptable. If somebody wants to implement this, let me know. |
Ok I tried to implement the 'cleanup' AppDomain approach, however it seems to be a bit fragile. Can somebody please test that this works in a real life scenario: #254. The only limitation is that you cannot use the default AppDomain otherwise it should just work. |
I could test it since I actually encountered the issue, but can you give a small note on how to do that (again sorry, I'm a bit new with C#/Nuget)? I just pull the sources, build a project and add the dlls instead of the nuget ones? |
I don't know if nuget has a build-in feature for that, but the following should work git clone https://github.com/Antaris/RazorEngine.git
cd RazorEngine
git checkout cleanup_temp_files
./build.sh # when in git bash or just build.cmd and then copy |
I could do a quick beta release as well if you feel more comfortable with that. |
If it's not too hard that would certainly be better for me:) |
Please try https://www.nuget.org/packages/RazorEngine/3.6.3-beta1 |
Ok, so I installed the package and used the example snippet from the isolation API page to create an AppDomain (https://antaris.github.io/RazorEngine/Isolation.html) - I was able to compile all the templates but the files were still left on the disk and lots of first chance System.UnauthorizedAccessException instances figured in the debug output. However, testing this actually showed that this is not a way to go for me - I embed the cshtml files in the assembly, and then to ship them over from the main domain to the isolation domain I need to make them serializable, which seems more difficult than just deleting the files by hand. |
Thanks for testing. Did it work (ie at least as before) or crash for you? If it did work by just replacing that's a good sign (this is still all work in progress), even if not all files have been deleted... And for the new AppDomain better do not use the isolation snippet, that's for a sandboxed domain, and sandboxing does hurt to make it work. Instead try this: static int Main(string[] args)
{
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
{
// RazorEngine cannot clean up from the default appdomain...
Console.WriteLine("Switching to secound AppDomain, for RazorEngine...");
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
var current = AppDomain.CurrentDomain;
// You only need to add strongnames when your appdomain is not a full trust environment.
var strongNames = new StrongName[0];
var domain = AppDomain.CreateDomain(
"MyMainDomain", null,
current.SetupInformation, new PermissionSet(PermissionState.Unrestricted),
strongNames);
return domain.ExecuteAssembly(Assembly.GetExecutingAssembly().Location);
}
var template =
@"@helper Display(int price) {
if (price == 0) {
<text>free</text>
} else {
<text>@price</text>
}
}
@Display(Model.MyPrice)";
TemplateServiceConfiguration templateConfig = new TemplateServiceConfiguration();
templateConfig.Debug = true;
var razorEngineService = RazorEngineService.Create((ITemplateServiceConfiguration)templateConfig);
razorEngineService.AddTemplate("Index.cshtml", template);
razorEngineService.Compile("Index.cshtml");
var result = razorEngineService.RunCompile("Index.cshtml", model: new { MyPrice = 0 });
System.Diagnostics.Debug.Assert(result.Trim() == "free");
return 0;
} I will make a new release 3.6.3-beta2 shortly which should do a better cleanup job. |
It worked the same as before, no crashes or other newly introduced problems:) When you have a new beta I can test it with the provided snippet, it seems much better suited than what I am doing with the embedding of html. |
Now available in https://www.nuget.org/packages/RazorEngine/3.6.3. I disabled the cleanup code on mono because of https://bugzilla.xamarin.com/show_bug.cgi?id=28369. Documentation is here. |
I noticed what appears to be a race condition with the cleanup code. We're using RazorEngine inside an ASP.Net Web Application to generate emails, and monitoring the Temp folder I can see the files being written with our AppPool identity (not the Defaut). When I recycle the AppPool (either manually w/ IIsReset, or if it times out), most of the time the temp files won't be deleted. Sometimes, it does work as expected, though, and the files are gone after a recycle. Using ProcMon, I can see that when it fails, we're getting the "CANNOT DELETE" result (and not "ACCESS DENIED"), so my guess is that the files are still locked. Maybe RazorEngine is trying to delete those files before releasing all locked resources, maybe a Thread.Sleep() would prevent / mitigate the risk? |
Sadly there is no bullet proof way to check if a What can happen is when you recycle the AppPool IIS tries to kill all threads with Abort (or it even kills the process). I'm not sure if RazorEngine can do anything in that situation. Any chance that you can get the stderr output of the process running RazorEngine? It is quite possible that it would help to explain what you are seeing. |
We now have #258 to load assemblies from memory instead of disk, this is a nice solution for small short lived applications... |
Use /nostdlib when we find a mscorlib (improves mono support) Added `DisableTempFileLocking` to load assemblies in memory (to prevent temp file locking), this is only recommended in a very limited amount of scenarios. Please be aware of the consequences before using it. Please read #244 for details. Note that this can introduce memory leaks to your application.
Hi, We've been using Razor Engine since before 3.3. We have been noticing some of the warning discussed here, but have not observed any problems yet. In a nutshell, would we be better off reverting back to 3.3? We don't have any budget to look at this in depth (or even to upgrade to the new API) and are not keen on experimenting with loading new AppDomains - again, no budget and running in an Azure Webjob. Is using 3.3 the safest option for us? |
@DavidRogersDev You have several options at this point:
You can just add this to the application startup code: config.DisableTempFileLocking = true; // loads the files in-memory (gives the templates full-trust permissions)
config.CachingProvider = new DefaultCachingProvider(t => {}); //disables the warnings
// Use the config
Engine.Razor = RazorEngineService.Create(config); // new API
Razor.SetTemplateService(new TemplateService(config)); // legacy API Just note that all the new Features like improved debugging or Isolation will not work. But you wouldn't have them by staying on an old version either. Of course you can stay on the old version, but I do not recommend it. If there are blocking issues which stop you from upgrading we should address them here. |
One more thing to note is: |
We just upgraded from 3.6.6 to 3.7.0 and noticed this in our azure web job logs. Is this normal behavior?
|
Here is a short summary of your options:
(*) Disable the RazorEngine Cleanup Logic |
This is running on azure via web jobs.. Just saying... you can't and On 6/4/15, matthid [email protected] wrote:
Thanks |
It removes isolation: |
Is this going to be fixed? The 3 options are bandaids, not solutions. I upgraded to 3.7.4 from 3.4.1 and I think I am going to revert. |
Reverting is in fact the same as using those options. Sadly there is no easy fix and I don't have much spare time atm. |
See Antaris/RazorEngine#244 & https://antaris.github.io/RazorEngine/#Temporary-files. The disk cache isn't needed for Pretzel.
Hello, I upgraded to I think there is a bug in the method If my analysis is correct, is it possible to correct it shortly or have I to let my workaround in my code ? |
@Greooo Nice catch 👍 thanks for the detailed report, fixed in https://www.nuget.org/packages/RazorEngine/3.7.7. Opeing a new issue makes management a bit easier though :) (as this is one already closed) |
We might have a way to fix this on .netcore after https://github.com/dotnet/coreclr/issues/552 |
This fix seems to be a part of 3.10.0 / 4.5.0-rc1 release but I could not find in documentation where/how to configure the path for the temp files. |
Why does this package even write to a temporary file? That alone can be a reason for me not using it. |
We've been using Razor for hosting a web interface for a service successfully so far, but since upgrading from 3.3.0 to 3.6.1 we've run into an issue - we get a ton of
System.UnauthorizedAccessException
at the end of the program when the finalizers are called. Basically Razor complains about not being able to delete files likeC:\Users\user\AppData\Local\Temp\RazorEngine_becmcjic.mja\CompiledRazorTemplates.Dynamic.RazorEngine_69ed0018c26e44dca13eba07dcb6bfd6.dll
, this happens in theCompilationData
finalizer:Before the upgrade no such issues were present. The biggest change during the upgrade was that now we precompile all the templates before firing up the web interface:
I've narrowed it down to a standalone sample:
This triggers the System.UnauthorizedAccessException on exit.
Since previously Razor was working quite nice already out of the box I actually haven't been digging very deep into the internals and have no idea how to approach this issue. Can anyone provide any suggestions or tell what additional information from my side would help to understand the issue? From my point of view, if the dll's are still loaded by the engine of course it won't be able to delete them, but that is just my two cents. Is there any way to make the engine unload everything?
The text was updated successfully, but these errors were encountered: