{"action":"created","comment":{"url":"https://api.github.com/repos/Particular/NServiceBus/pulls/comments/47638016","id":47638016,"diff_hunk":"@@ -1,189 +1,109 @@\n namespace NServiceBus\n {\n     using System;\n-    using System.Collections.Concurrent;\n     using System.Collections.Generic;\n-    using System.IO;\n     using System.Linq;\n-    using System.Threading;\n-    using Janitor;\n+    using System.Threading.Tasks;\n+    using System.Xml.Linq;\n+    using NServiceBus.Features;\n     using NServiceBus.Logging;\n+    using NServiceBus.Settings;\n \n-    [SkipWeaving]\n-    class FileRoutingTable : IDisposable\n+    class FileRoutingTable : FeatureStartupTask\n     {\n-        public FileRoutingTable(string basePath, TimeSpan timeToWaitBeforeRaisingFileChangedEvent)\n+        static readonly ILog log = LogManager.GetLogger(typeof(FileRoutingTable));\n+\n+        Dictionary<Endpoint, HashSet<EndpointInstance>> instanceMap = new Dictionary<Endpoint, HashSet<EndpointInstance>>();\n+        ReadOnlySettings settings;\n+        string filePath;\n+        TimeSpan checkInterval;\n+        IAsyncTimer timer;\n+        IRoutingFileAccess fileAccess;\n+        FileRoutingTableParser parser = new FileRoutingTableParser();\n+        int maxLoadAttempts;\n+        \n+\n+        public FileRoutingTable(string filePath, TimeSpan checkInterval, IAsyncTimer timer, IRoutingFileAccess fileAccess, int maxLoadAttempts, ReadOnlySettings settings)\n         {\n-            this.basePath = basePath;\n-            this.timeToWaitBeforeRaisingFileChangedEvent = timeToWaitBeforeRaisingFileChangedEvent;\n+            this.settings = settings;\n+            this.filePath = filePath;\n+            this.checkInterval = checkInterval;\n+            this.timer = timer;\n+            this.fileAccess = fileAccess;\n+            this.maxLoadAttempts = maxLoadAttempts;\n         }\n \n-        public IEnumerable<EndpointInstance> GetInstances(Endpoint endpoint)\n+        protected override async Task OnStart(IBusContext context)\n         {\n-            logger.DebugFormat(\"Request routes for {0}.\", endpoint);\n+            var endpointInstances = settings.Get<EndpointInstances>();\n+            endpointInstances.AddDynamic(FindInstances);\n \n-            CacheRoute routes;\n-            if (!routeMapping.TryGetValue(endpoint.ToString(), out routes))\n-            {\n-                UpdateMapping(endpoint.ToString(), true);\n-\n-                if (!routeMapping.TryGetValue(endpoint.ToString(), out routes))\n-                {\n-                    yield break;\n-                }\n-            }\n-            foreach (var route in routes.Routes)\n-            {\n-                var discriminators = route.Split(new []{':'},StringSplitOptions.None);\n-                if (discriminators.Length == 2)\n-                {\n-                    var userDiscriminator = NullIfEmptyString(discriminators[0].Trim());\n-                    var transportDiscriminator = NullIfEmptyString(discriminators[1].Trim());\n-\n-                    yield return new EndpointInstance(endpoint, userDiscriminator, transportDiscriminator);\n-                }\n-                else\n-                {\n-                    logger.Info($\"Invalid route {route}. Expecting <userDiscriminator>:<transportDiscriminator> format\");\n-                }\n-            }\n-        }\n+            await ReloadData();\n \n-        static string NullIfEmptyString(string value)\n-        {\n-            return value.Equals(\"\", StringComparison.InvariantCultureIgnoreCase) \n-                ? null \n-                : value;\n+            timer.Start(ReloadData, checkInterval, ex => log.Error(\"Error while reading routing table\", ex));\n         }\n \n-        void StartMonitoring(string basePath, string queueName, string fileName)\n+        async Task ReloadData()\n         {\n-            monitoringFiles.Add(new MonitorFileChanges(basePath, queueName, fileName, timeToWaitBeforeRaisingFileChangedEvent, s => UpdateMapping(s, false)));\n-        }\n+            var doc = await ReadFileWithRetries().ConfigureAwait(false);\n+            var instances = parser.Parse(doc);\n+            var newInsatnceMap = new Dictionary<Endpoint, HashSet<EndpointInstance>>();","path":"src/NServiceBus.Core/Routing/FileBasedDynamicRouting/FileRoutingTable.cs","position":96,"original_position":95,"commit_id":"ddf0dd36b7b35795cbf5785c66d0117887374636","original_commit_id":"551e91d0c7ed2b4119bbc2001b06e08c99dc3dad","user":{"login":"SzymonPobiega","id":163318,"avatar_url":"https://avatars.githubusercontent.com/u/163318?v=3","gravatar_id":"","url":"https://api.github.com/users/SzymonPobiega","html_url":"https://github.com/SzymonPobiega","followers_url":"https://api.github.com/users/SzymonPobiega/followers","following_url":"https://api.github.com/users/SzymonPobiega/following{/other_user}","gists_url":"https://api.github.com/users/SzymonPobiega/gists{/gist_id}","starred_url":"https://api.github.com/users/SzymonPobiega/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SzymonPobiega/subscriptions","organizations_url":"https://api.github.com/users/SzymonPobiega/orgs","repos_url":"https://api.github.com/users/SzymonPobiega/repos","events_url":"https://api.github.com/users/SzymonPobiega/events{/privacy}","received_events_url":"https://api.github.com/users/SzymonPobiega/received_events","type":"User","site_admin":false},"body":":+1: ","created_at":"2015-12-15T13:56:31Z","updated_at":"2015-12-15T13:56:31Z","html_url":"https://github.com/Particular/NServiceBus/pull/3195#discussion_r47638016","pull_request_url":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195","_links":{"self":{"href":"https://api.github.com/repos/Particular/NServiceBus/pulls/comments/47638016"},"html":{"href":"https://github.com/Particular/NServiceBus/pull/3195#discussion_r47638016"},"pull_request":{"href":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195"}}},"pull_request":{"url":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195","id":53249064,"html_url":"https://github.com/Particular/NServiceBus/pull/3195","diff_url":"https://github.com/Particular/NServiceBus/pull/3195.diff","patch_url":"https://github.com/Particular/NServiceBus/pull/3195.patch","issue_url":"https://api.github.com/repos/Particular/NServiceBus/issues/3195","number":3195,"state":"open","locked":false,"title":"XML file based routing","user":{"login":"SzymonPobiega","id":163318,"avatar_url":"https://avatars.githubusercontent.com/u/163318?v=3","gravatar_id":"","url":"https://api.github.com/users/SzymonPobiega","html_url":"https://github.com/SzymonPobiega","followers_url":"https://api.github.com/users/SzymonPobiega/followers","following_url":"https://api.github.com/users/SzymonPobiega/following{/other_user}","gists_url":"https://api.github.com/users/SzymonPobiega/gists{/gist_id}","starred_url":"https://api.github.com/users/SzymonPobiega/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SzymonPobiega/subscriptions","organizations_url":"https://api.github.com/users/SzymonPobiega/orgs","repos_url":"https://api.github.com/users/SzymonPobiega/repos","events_url":"https://api.github.com/users/SzymonPobiega/events{/privacy}","received_events_url":"https://api.github.com/users/SzymonPobiega/received_events","type":"User","site_admin":false},"body":"This pull refactors the existing file-based routing to be more production ready. In order to do it properly I've redesigned the `Endpoint` class, replacing the `transport discriminator` with a generic property bag. The usage is following\r\n\r\n```\r\nvar i = new EndpointInstance(\"Sales\").SetProperty(\"schema\", \"nsb\");\r\n```\r\n\r\nTransports might provide specific extension methods:\r\n\r\n```\r\n//MSMQ queue@machine\r\nvar i = new EndpointInstance(\"Sales\").AtMachine(\"MyMachine\");\r\n```\r\n\r\nThe bag in future might support other properties (e.g. SQL Server's `schema` or gateway's `site`).\r\n\r\nOther changes:\r\n * Removed exception when trying to scale out without providing user or transport discriminator. This is because the bag that replaced transport discriminator not necessarily guarantees name uniqueness.\r\n * Renamed `UniqueQueuePerEndpointInstance` to `UniqueInstanceDiscriminator`\r\n\r\n### XML file based routing\r\n\r\nThe sample XML file is following:\r\n\r\n```\r\n<endpoints>\r\n    <endpoint name=\"A\">\r\n        <instance discriminator=\"D1\" prop1=\"V1\" prop2=\"V2\"/>\r\n        <instance prop3=\"V3\" prop4=\"V4\"/>\r\n    </endpoint>\r\n    <endpoint name=\"B\">\r\n        <instance discriminator=\"D2\" prop5=\"V5\" prop6=\"V6\"/>\r\n    </endpoint>\r\n</endpoints>\r\n```\r\n\r\nThe only mandatory attribute is endpoint `name`. Parsing is lax, using DOM. The file based routing table checks the file every couple of seconds (removed file system watcher). If the file does not exist when starting up, the endpoint fails to start. If the file is deleted during run-time, errors are logged but endpoint is not killed.\r\n\r\n","created_at":"2015-12-10T11:57:17Z","updated_at":"2015-12-15T13:56:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b9c0463b9f7ba404cbc98fd3fd0cef34d32df568","assignee":{"login":"SzymonPobiega","id":163318,"avatar_url":"https://avatars.githubusercontent.com/u/163318?v=3","gravatar_id":"","url":"https://api.github.com/users/SzymonPobiega","html_url":"https://github.com/SzymonPobiega","followers_url":"https://api.github.com/users/SzymonPobiega/followers","following_url":"https://api.github.com/users/SzymonPobiega/following{/other_user}","gists_url":"https://api.github.com/users/SzymonPobiega/gists{/gist_id}","starred_url":"https://api.github.com/users/SzymonPobiega/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SzymonPobiega/subscriptions","organizations_url":"https://api.github.com/users/SzymonPobiega/orgs","repos_url":"https://api.github.com/users/SzymonPobiega/repos","events_url":"https://api.github.com/users/SzymonPobiega/events{/privacy}","received_events_url":"https://api.github.com/users/SzymonPobiega/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195/commits","review_comments_url":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195/comments","review_comment_url":"https://api.github.com/repos/Particular/NServiceBus/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Particular/NServiceBus/issues/3195/comments","statuses_url":"https://api.github.com/repos/Particular/NServiceBus/statuses/ddf0dd36b7b35795cbf5785c66d0117887374636","head":{"label":"Particular:endpoint-instance-properties","ref":"endpoint-instance-properties","sha":"ddf0dd36b7b35795cbf5785c66d0117887374636","user":{"login":"Particular","id":5038441,"avatar_url":"https://avatars.githubusercontent.com/u/5038441?v=3","gravatar_id":"","url":"https://api.github.com/users/Particular","html_url":"https://github.com/Particular","followers_url":"https://api.github.com/users/Particular/followers","following_url":"https://api.github.com/users/Particular/following{/other_user}","gists_url":"https://api.github.com/users/Particular/gists{/gist_id}","starred_url":"https://api.github.com/users/Particular/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Particular/subscriptions","organizations_url":"https://api.github.com/users/Particular/orgs","repos_url":"https://api.github.com/users/Particular/repos","events_url":"https://api.github.com/users/Particular/events{/privacy}","received_events_url":"https://api.github.com/users/Particular/received_events","type":"Organization","site_admin":false},"repo":{"id":1056713,"name":"NServiceBus","full_name":"Particular/NServiceBus","owner":{"login":"Particular","id":5038441,"avatar_url":"https://avatars.githubusercontent.com/u/5038441?v=3","gravatar_id":"","url":"https://api.github.com/users/Particular","html_url":"https://github.com/Particular","followers_url":"https://api.github.com/users/Particular/followers","following_url":"https://api.github.com/users/Particular/following{/other_user}","gists_url":"https://api.github.com/users/Particular/gists{/gist_id}","starred_url":"https://api.github.com/users/Particular/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Particular/subscriptions","organizations_url":"https://api.github.com/users/Particular/orgs","repos_url":"https://api.github.com/users/Particular/repos","events_url":"https://api.github.com/users/Particular/events{/privacy}","received_events_url":"https://api.github.com/users/Particular/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Particular/NServiceBus","description":"The most popular service bus for .NET","fork":false,"url":"https://api.github.com/repos/Particular/NServiceBus","forks_url":"https://api.github.com/repos/Particular/NServiceBus/forks","keys_url":"https://api.github.com/repos/Particular/NServiceBus/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Particular/NServiceBus/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Particular/NServiceBus/teams","hooks_url":"https://api.github.com/repos/Particular/NServiceBus/hooks","issue_events_url":"https://api.github.com/repos/Particular/NServiceBus/issues/events{/number}","events_url":"https://api.github.com/repos/Particular/NServiceBus/events","assignees_url":"https://api.github.com/repos/Particular/NServiceBus/assignees{/user}","branches_url":"https://api.github.com/repos/Particular/NServiceBus/branches{/branch}","tags_url":"https://api.github.com/repos/Particular/NServiceBus/tags","blobs_url":"https://api.github.com/repos/Particular/NServiceBus/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Particular/NServiceBus/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Particular/NServiceBus/git/refs{/sha}","trees_url":"https://api.github.com/repos/Particular/NServiceBus/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Particular/NServiceBus/statuses/{sha}","languages_url":"https://api.github.com/repos/Particular/NServiceBus/languages","stargazers_url":"https://api.github.com/repos/Particular/NServiceBus/stargazers","contributors_url":"https://api.github.com/repos/Particular/NServiceBus/contributors","subscribers_url":"https://api.github.com/repos/Particular/NServiceBus/subscribers","subscription_url":"https://api.github.com/repos/Particular/NServiceBus/subscription","commits_url":"https://api.github.com/repos/Particular/NServiceBus/commits{/sha}","git_commits_url":"https://api.github.com/repos/Particular/NServiceBus/git/commits{/sha}","comments_url":"https://api.github.com/repos/Particular/NServiceBus/comments{/number}","issue_comment_url":"https://api.github.com/repos/Particular/NServiceBus/issues/comments{/number}","contents_url":"https://api.github.com/repos/Particular/NServiceBus/contents/{+path}","compare_url":"https://api.github.com/repos/Particular/NServiceBus/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Particular/NServiceBus/merges","archive_url":"https://api.github.com/repos/Particular/NServiceBus/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Particular/NServiceBus/downloads","issues_url":"https://api.github.com/repos/Particular/NServiceBus/issues{/number}","pulls_url":"https://api.github.com/repos/Particular/NServiceBus/pulls{/number}","milestones_url":"https://api.github.com/repos/Particular/NServiceBus/milestones{/number}","notifications_url":"https://api.github.com/repos/Particular/NServiceBus/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Particular/NServiceBus/labels{/name}","releases_url":"https://api.github.com/repos/Particular/NServiceBus/releases{/id}","created_at":"2010-11-06T12:54:52Z","updated_at":"2015-12-15T10:43:39Z","pushed_at":"2015-12-15T13:48:41Z","git_url":"git://github.com/Particular/NServiceBus.git","ssh_url":"git@github.com:Particular/NServiceBus.git","clone_url":"https://github.com/Particular/NServiceBus.git","svn_url":"https://github.com/Particular/NServiceBus","homepage":"http://particular.net/nservicebus","size":158222,"stargazers_count":841,"watchers_count":841,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":456,"mirror_url":null,"open_issues_count":210,"forks":456,"open_issues":210,"watchers":841,"default_branch":"develop"}},"base":{"label":"Particular:develop","ref":"develop","sha":"551f1c1d14e2d84241b499eadfe3b6ef73f896ae","user":{"login":"Particular","id":5038441,"avatar_url":"https://avatars.githubusercontent.com/u/5038441?v=3","gravatar_id":"","url":"https://api.github.com/users/Particular","html_url":"https://github.com/Particular","followers_url":"https://api.github.com/users/Particular/followers","following_url":"https://api.github.com/users/Particular/following{/other_user}","gists_url":"https://api.github.com/users/Particular/gists{/gist_id}","starred_url":"https://api.github.com/users/Particular/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Particular/subscriptions","organizations_url":"https://api.github.com/users/Particular/orgs","repos_url":"https://api.github.com/users/Particular/repos","events_url":"https://api.github.com/users/Particular/events{/privacy}","received_events_url":"https://api.github.com/users/Particular/received_events","type":"Organization","site_admin":false},"repo":{"id":1056713,"name":"NServiceBus","full_name":"Particular/NServiceBus","owner":{"login":"Particular","id":5038441,"avatar_url":"https://avatars.githubusercontent.com/u/5038441?v=3","gravatar_id":"","url":"https://api.github.com/users/Particular","html_url":"https://github.com/Particular","followers_url":"https://api.github.com/users/Particular/followers","following_url":"https://api.github.com/users/Particular/following{/other_user}","gists_url":"https://api.github.com/users/Particular/gists{/gist_id}","starred_url":"https://api.github.com/users/Particular/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Particular/subscriptions","organizations_url":"https://api.github.com/users/Particular/orgs","repos_url":"https://api.github.com/users/Particular/repos","events_url":"https://api.github.com/users/Particular/events{/privacy}","received_events_url":"https://api.github.com/users/Particular/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Particular/NServiceBus","description":"The most popular service bus for .NET","fork":false,"url":"https://api.github.com/repos/Particular/NServiceBus","forks_url":"https://api.github.com/repos/Particular/NServiceBus/forks","keys_url":"https://api.github.com/repos/Particular/NServiceBus/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Particular/NServiceBus/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Particular/NServiceBus/teams","hooks_url":"https://api.github.com/repos/Particular/NServiceBus/hooks","issue_events_url":"https://api.github.com/repos/Particular/NServiceBus/issues/events{/number}","events_url":"https://api.github.com/repos/Particular/NServiceBus/events","assignees_url":"https://api.github.com/repos/Particular/NServiceBus/assignees{/user}","branches_url":"https://api.github.com/repos/Particular/NServiceBus/branches{/branch}","tags_url":"https://api.github.com/repos/Particular/NServiceBus/tags","blobs_url":"https://api.github.com/repos/Particular/NServiceBus/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Particular/NServiceBus/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Particular/NServiceBus/git/refs{/sha}","trees_url":"https://api.github.com/repos/Particular/NServiceBus/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Particular/NServiceBus/statuses/{sha}","languages_url":"https://api.github.com/repos/Particular/NServiceBus/languages","stargazers_url":"https://api.github.com/repos/Particular/NServiceBus/stargazers","contributors_url":"https://api.github.com/repos/Particular/NServiceBus/contributors","subscribers_url":"https://api.github.com/repos/Particular/NServiceBus/subscribers","subscription_url":"https://api.github.com/repos/Particular/NServiceBus/subscription","commits_url":"https://api.github.com/repos/Particular/NServiceBus/commits{/sha}","git_commits_url":"https://api.github.com/repos/Particular/NServiceBus/git/commits{/sha}","comments_url":"https://api.github.com/repos/Particular/NServiceBus/comments{/number}","issue_comment_url":"https://api.github.com/repos/Particular/NServiceBus/issues/comments{/number}","contents_url":"https://api.github.com/repos/Particular/NServiceBus/contents/{+path}","compare_url":"https://api.github.com/repos/Particular/NServiceBus/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Particular/NServiceBus/merges","archive_url":"https://api.github.com/repos/Particular/NServiceBus/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Particular/NServiceBus/downloads","issues_url":"https://api.github.com/repos/Particular/NServiceBus/issues{/number}","pulls_url":"https://api.github.com/repos/Particular/NServiceBus/pulls{/number}","milestones_url":"https://api.github.com/repos/Particular/NServiceBus/milestones{/number}","notifications_url":"https://api.github.com/repos/Particular/NServiceBus/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Particular/NServiceBus/labels{/name}","releases_url":"https://api.github.com/repos/Particular/NServiceBus/releases{/id}","created_at":"2010-11-06T12:54:52Z","updated_at":"2015-12-15T10:43:39Z","pushed_at":"2015-12-15T13:48:41Z","git_url":"git://github.com/Particular/NServiceBus.git","ssh_url":"git@github.com:Particular/NServiceBus.git","clone_url":"https://github.com/Particular/NServiceBus.git","svn_url":"https://github.com/Particular/NServiceBus","homepage":"http://particular.net/nservicebus","size":158222,"stargazers_count":841,"watchers_count":841,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":456,"mirror_url":null,"open_issues_count":210,"forks":456,"open_issues":210,"watchers":841,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195"},"html":{"href":"https://github.com/Particular/NServiceBus/pull/3195"},"issue":{"href":"https://api.github.com/repos/Particular/NServiceBus/issues/3195"},"comments":{"href":"https://api.github.com/repos/Particular/NServiceBus/issues/3195/comments"},"review_comments":{"href":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195/comments"},"review_comment":{"href":"https://api.github.com/repos/Particular/NServiceBus/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Particular/NServiceBus/pulls/3195/commits"},"statuses":{"href":"https://api.github.com/repos/Particular/NServiceBus/statuses/ddf0dd36b7b35795cbf5785c66d0117887374636"}}}}