{"action":"created","comment":{"url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments/91919993","pull_request_review_id":12425911,"id":91919993,"diff_hunk":"@@ -0,0 +1,92 @@\n+﻿using System;\n+using System.Collections.Generic;\n+using System.IO;\n+using System.Linq;\n+using System.Text;\n+using System.Threading.Tasks;\n+using ImageProcessor.Web.Caching;\n+using NUnit.Framework;\n+\n+namespace ImageProcessor.UnitTests.ImageCache\n+{\n+    [TestFixture]\n+    public class DiskCacheTests","path":"tests/ImageProcessor.UnitTests/ImageCache/DiskCacheTests.cs","position":13,"original_position":13,"commit_id":"322a4512c65427516c177eab870237fdbb145bb9","original_commit_id":"322a4512c65427516c177eab870237fdbb145bb9","user":{"login":"JimBobSquarePants","id":385879,"avatar_url":"https://avatars.githubusercontent.com/u/385879?v=3","gravatar_id":"","url":"https://api.github.com/users/JimBobSquarePants","html_url":"https://github.com/JimBobSquarePants","followers_url":"https://api.github.com/users/JimBobSquarePants/followers","following_url":"https://api.github.com/users/JimBobSquarePants/following{/other_user}","gists_url":"https://api.github.com/users/JimBobSquarePants/gists{/gist_id}","starred_url":"https://api.github.com/users/JimBobSquarePants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JimBobSquarePants/subscriptions","organizations_url":"https://api.github.com/users/JimBobSquarePants/orgs","repos_url":"https://api.github.com/users/JimBobSquarePants/repos","events_url":"https://api.github.com/users/JimBobSquarePants/events{/privacy}","received_events_url":"https://api.github.com/users/JimBobSquarePants/received_events","type":"User","site_admin":false},"body":"Good tests... I'm terrible for this in this project.","created_at":"2016-12-12T10:28:03Z","updated_at":"2016-12-12T10:30:55Z","html_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/521#discussion_r91919993","pull_request_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521","_links":{"self":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments/91919993"},"html":{"href":"https://github.com/JimBobSquarePants/ImageProcessor/pull/521#discussion_r91919993"},"pull_request":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521"}}},"pull_request":{"url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521","id":97509921,"html_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/521","diff_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/521.diff","patch_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/521.patch","issue_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/521","number":521,"state":"open","locked":false,"title":"Disk cache outside webroot","user":{"login":"Shazwazza","id":1742685,"avatar_url":"https://avatars.githubusercontent.com/u/1742685?v=3","gravatar_id":"","url":"https://api.github.com/users/Shazwazza","html_url":"https://github.com/Shazwazza","followers_url":"https://api.github.com/users/Shazwazza/followers","following_url":"https://api.github.com/users/Shazwazza/following{/other_user}","gists_url":"https://api.github.com/users/Shazwazza/gists{/gist_id}","starred_url":"https://api.github.com/users/Shazwazza/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shazwazza/subscriptions","organizations_url":"https://api.github.com/users/Shazwazza/orgs","repos_url":"https://api.github.com/users/Shazwazza/repos","events_url":"https://api.github.com/users/Shazwazza/events{/privacy}","received_events_url":"https://api.github.com/users/Shazwazza/received_events","type":"User","site_admin":false},"body":"The PR does quite a few things:\r\n\r\n* Allows for having the IP disk cache to exist outside of the web root\r\n* The paths configured for the IP disk cache can now be absolute paths or virtual paths, virtual paths can also be relative to go beyond the web root (i.e. \"~/../OutsideOfWebRoot\")\r\n* Adds unit tests for resolving the cache folder, absolute and virtual paths\r\n* Better performance for the disk cache - it now only validates and resolves the cache paths once (lazily) per application, before it did this for every file\r\n* Adds a new configuration option to opt-in to automatically creating file watchers for every cached image -> this is actually the main cause for FCN problems\r\n* Updates the _Layout.cshtml page to show more FCN details\r\n* Adds lots of notes regarding what has been changed and a few more TODOs (though these TODOs are outside of this PR scope)\r\n\r\nOk, so I'll try to explain as much as I can here :) \r\n\r\n* By default in ASP.NET with FcnMode=\"NotSet\", ASP.NET will create a watcher for every sub folder in your application that is inside of the web root. It will create these watchers lazily, so once an IP cached image is fetched a watcher would be created for the folder it exists in\r\n* Using FcnMode=\"Single\", ASP.NET will create a single watcher with a larger buffer but will still monitor all sub folders in your application that is inside the web root. \r\n\r\nSo why choose one over another? Well there's a couple of reasons. When running on Azure Web apps, the file server is a remote file server (i.e. UNC Share). When you have FcnMode=\"NotSet\" then having all of these watchers will actually slow down the file server quite a lot... this is almost true... Azure Web apps runs a custom windows kernel so MS is doing something rather special to ensure that these watchers don't slow down the file servers, however other hosts like Umbraco Cloud aren't so lucky and run a normal Windows kernel. So running FcnMode=\"Single\" on Umbraco Cloud makes the file system much happier, same would probably be true for any traditionally hosted IIS site that uses remote file system UNC file paths. However, the downfall of \"Single\" mode is that the buffer can overflow more easily because there's only one watcher watching a ton of folders. Since Image Processor creates quite a lot of folders and if people use Image Processor extensively then they may see this buffer overflow with errors like \"Overwhelming change notifications\" (among others). \r\n\r\nBeing able to store Image Processor cache files outside of the web root will reduce the amount of directories watched which this PR enables. __However__ what I discovered is that even if we do this, there are still file watchers being created for every Image Process cache file! This is because by default when Image Process adds the result to the memory cache, it does so by adding a file dependency. This is so that the in memory cache can be invalidated if the cache files on disk changes. I don't really think it's a big requirement for people to have that functionality and it drastically increases the number of files watchers. In actual fact, I believe this to be the main root cause of FCN issues with Image Processor.\r\n\r\nThere is a new config option: `useFileChangeMonitors` and by default this will be `false`. This is sort of a breaking change (but is it?!) since it was true before. This one change could in fact reduce the number of file watchers so that FCN is no longer an issue. You can test this with the test web app by togging this value. But, even if this is false, you'll see that the \"FileChangesMonitor aliases hash table\" on the test website still grows anytime an Image Process cache file is accessed when it's in the web root so I'm fairly sure that the \"Single\" watcher is still watching those. \r\n\r\nSo when the cache is configured outside of the web root like with \"~/../OutsideOfWebRoot\" (make sure that folder exists and that the IIS user has access to it!), then load up and browse around the test website and you'll notice a vast reduction in the amount of image processor cache folders being watched, along with the default `false` setting for  `useFileChangeMonitors`, the number of folders monitored is now very low.","created_at":"2016-12-12T08:12:17Z","updated_at":"2016-12-12T10:30:55Z","closed_at":null,"merged_at":null,"merge_commit_sha":"36a7839fdbdda9b3acabdde97b4a7c50a76e250d","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521/commits","review_comments_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521/comments","review_comment_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments{/number}","comments_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/521/comments","statuses_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/statuses/322a4512c65427516c177eab870237fdbb145bb9","head":{"label":"Shazwazza:disk-cache-outside-webroot","ref":"disk-cache-outside-webroot","sha":"322a4512c65427516c177eab870237fdbb145bb9","user":{"login":"Shazwazza","id":1742685,"avatar_url":"https://avatars.githubusercontent.com/u/1742685?v=3","gravatar_id":"","url":"https://api.github.com/users/Shazwazza","html_url":"https://github.com/Shazwazza","followers_url":"https://api.github.com/users/Shazwazza/followers","following_url":"https://api.github.com/users/Shazwazza/following{/other_user}","gists_url":"https://api.github.com/users/Shazwazza/gists{/gist_id}","starred_url":"https://api.github.com/users/Shazwazza/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shazwazza/subscriptions","organizations_url":"https://api.github.com/users/Shazwazza/orgs","repos_url":"https://api.github.com/users/Shazwazza/repos","events_url":"https://api.github.com/users/Shazwazza/events{/privacy}","received_events_url":"https://api.github.com/users/Shazwazza/received_events","type":"User","site_admin":false},"repo":{"id":54461689,"name":"ImageProcessor","full_name":"Shazwazza/ImageProcessor","owner":{"login":"Shazwazza","id":1742685,"avatar_url":"https://avatars.githubusercontent.com/u/1742685?v=3","gravatar_id":"","url":"https://api.github.com/users/Shazwazza","html_url":"https://github.com/Shazwazza","followers_url":"https://api.github.com/users/Shazwazza/followers","following_url":"https://api.github.com/users/Shazwazza/following{/other_user}","gists_url":"https://api.github.com/users/Shazwazza/gists{/gist_id}","starred_url":"https://api.github.com/users/Shazwazza/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shazwazza/subscriptions","organizations_url":"https://api.github.com/users/Shazwazza/orgs","repos_url":"https://api.github.com/users/Shazwazza/repos","events_url":"https://api.github.com/users/Shazwazza/events{/privacy}","received_events_url":"https://api.github.com/users/Shazwazza/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Shazwazza/ImageProcessor","description":":camera: A cross-platform library for processing of image files written in C#","fork":true,"url":"https://api.github.com/repos/Shazwazza/ImageProcessor","forks_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/forks","keys_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/teams","hooks_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/hooks","issue_events_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/issues/events{/number}","events_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/events","assignees_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/assignees{/user}","branches_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/branches{/branch}","tags_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/tags","blobs_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/git/refs{/sha}","trees_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/statuses/{sha}","languages_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/languages","stargazers_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/stargazers","contributors_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/contributors","subscribers_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/subscribers","subscription_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/subscription","commits_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/commits{/sha}","git_commits_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/git/commits{/sha}","comments_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/comments{/number}","issue_comment_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/issues/comments{/number}","contents_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/contents/{+path}","compare_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/merges","archive_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/downloads","issues_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/issues{/number}","pulls_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/pulls{/number}","milestones_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/milestones{/number}","notifications_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/labels{/name}","releases_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/releases{/id}","deployments_url":"https://api.github.com/repos/Shazwazza/ImageProcessor/deployments","created_at":"2016-03-22T09:19:37Z","updated_at":"2016-03-22T09:19:38Z","pushed_at":"2016-12-12T07:49:23Z","git_url":"git://github.com/Shazwazza/ImageProcessor.git","ssh_url":"git@github.com:Shazwazza/ImageProcessor.git","clone_url":"https://github.com/Shazwazza/ImageProcessor.git","svn_url":"https://github.com/Shazwazza/ImageProcessor","homepage":"http://imageprocessor.org","size":414409,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"Core"}},"base":{"label":"JimBobSquarePants:develop","ref":"develop","sha":"e986b06d010a4858b3a1442767d5ae8e329cc16f","user":{"login":"JimBobSquarePants","id":385879,"avatar_url":"https://avatars.githubusercontent.com/u/385879?v=3","gravatar_id":"","url":"https://api.github.com/users/JimBobSquarePants","html_url":"https://github.com/JimBobSquarePants","followers_url":"https://api.github.com/users/JimBobSquarePants/followers","following_url":"https://api.github.com/users/JimBobSquarePants/following{/other_user}","gists_url":"https://api.github.com/users/JimBobSquarePants/gists{/gist_id}","starred_url":"https://api.github.com/users/JimBobSquarePants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JimBobSquarePants/subscriptions","organizations_url":"https://api.github.com/users/JimBobSquarePants/orgs","repos_url":"https://api.github.com/users/JimBobSquarePants/repos","events_url":"https://api.github.com/users/JimBobSquarePants/events{/privacy}","received_events_url":"https://api.github.com/users/JimBobSquarePants/received_events","type":"User","site_admin":false},"repo":{"id":4471160,"name":"ImageProcessor","full_name":"JimBobSquarePants/ImageProcessor","owner":{"login":"JimBobSquarePants","id":385879,"avatar_url":"https://avatars.githubusercontent.com/u/385879?v=3","gravatar_id":"","url":"https://api.github.com/users/JimBobSquarePants","html_url":"https://github.com/JimBobSquarePants","followers_url":"https://api.github.com/users/JimBobSquarePants/followers","following_url":"https://api.github.com/users/JimBobSquarePants/following{/other_user}","gists_url":"https://api.github.com/users/JimBobSquarePants/gists{/gist_id}","starred_url":"https://api.github.com/users/JimBobSquarePants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JimBobSquarePants/subscriptions","organizations_url":"https://api.github.com/users/JimBobSquarePants/orgs","repos_url":"https://api.github.com/users/JimBobSquarePants/repos","events_url":"https://api.github.com/users/JimBobSquarePants/events{/privacy}","received_events_url":"https://api.github.com/users/JimBobSquarePants/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/JimBobSquarePants/ImageProcessor","description":":camera: A fluent wrapper around System.Drawing for the processing of image files.","fork":false,"url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor","forks_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/forks","keys_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/teams","hooks_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/hooks","issue_events_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/events{/number}","events_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/events","assignees_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/assignees{/user}","branches_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/branches{/branch}","tags_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/tags","blobs_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/refs{/sha}","trees_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/statuses/{sha}","languages_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/languages","stargazers_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/stargazers","contributors_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/contributors","subscribers_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/subscribers","subscription_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/subscription","commits_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/commits{/sha}","git_commits_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/commits{/sha}","comments_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/comments{/number}","issue_comment_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/comments{/number}","contents_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/contents/{+path}","compare_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/merges","archive_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/downloads","issues_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues{/number}","pulls_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls{/number}","milestones_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/milestones{/number}","notifications_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/labels{/name}","releases_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/releases{/id}","deployments_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/deployments","created_at":"2012-05-28T14:11:35Z","updated_at":"2016-12-12T10:11:13Z","pushed_at":"2016-12-12T08:12:17Z","git_url":"git://github.com/JimBobSquarePants/ImageProcessor.git","ssh_url":"git@github.com:JimBobSquarePants/ImageProcessor.git","clone_url":"https://github.com/JimBobSquarePants/ImageProcessor.git","svn_url":"https://github.com/JimBobSquarePants/ImageProcessor","homepage":"http://imageprocessor.org","size":455321,"stargazers_count":1493,"watchers_count":1493,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":337,"mirror_url":null,"open_issues_count":14,"forks":337,"open_issues":14,"watchers":1493,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521"},"html":{"href":"https://github.com/JimBobSquarePants/ImageProcessor/pull/521"},"issue":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/521"},"comments":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/521/comments"},"review_comments":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521/comments"},"review_comment":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/521/commits"},"statuses":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/statuses/322a4512c65427516c177eab870237fdbb145bb9"}}}}
{"actor":{"display_login":"JimBobSquarePants"}}