{"action":"closed","issue":{"url":"https://api.github.com/repos/aspnet/EntityFrameworkCore/issues/9784","repository_url":"https://api.github.com/repos/aspnet/EntityFrameworkCore","labels_url":"https://api.github.com/repos/aspnet/EntityFrameworkCore/issues/9784/labels{/name}","comments_url":"https://api.github.com/repos/aspnet/EntityFrameworkCore/issues/9784/comments","events_url":"https://api.github.com/repos/aspnet/EntityFrameworkCore/issues/9784/events","html_url":"https://github.com/aspnet/EntityFrameworkCore/issues/9784","id":257148908,"number":9784,"title":"Infinite loop in compiled query cache","user":{"login":"meikeric","id":538094,"avatar_url":"https://avatars3.githubusercontent.com/u/538094?v=4","gravatar_id":"","url":"https://api.github.com/users/meikeric","html_url":"https://github.com/meikeric","followers_url":"https://api.github.com/users/meikeric/followers","following_url":"https://api.github.com/users/meikeric/following{/other_user}","gists_url":"https://api.github.com/users/meikeric/gists{/gist_id}","starred_url":"https://api.github.com/users/meikeric/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meikeric/subscriptions","organizations_url":"https://api.github.com/users/meikeric/orgs","repos_url":"https://api.github.com/users/meikeric/repos","events_url":"https://api.github.com/users/meikeric/events{/privacy}","received_events_url":"https://api.github.com/users/meikeric/received_events","type":"User","site_admin":false},"labels":[{"id":600873665,"url":"https://api.github.com/repos/aspnet/EntityFrameworkCore/labels/closed-not-needed","name":"closed-not-needed","color":"777777","default":false}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2017-09-12T19:06:48Z","updated_at":"2017-09-14T16:34:30Z","closed_at":"2017-09-14T16:34:30Z","author_association":"NONE","body":"There is still an infinite loop opportunity in CompiledQueryCache.cs, RE: #7230\r\n\r\nIf the _querySyncObjects ConcurrentDictionary continuously fails to add for whatever reason, This method will never return.  Some type of guard, max attempt counter, or even a timeout comparing times needs to be implemented.\r\n\r\nIssue Core (Note the Goto-Retry Loop)\r\n\r\n        private Func<QueryContext, TFunc> GetOrAddQueryCore<TFunc>(\r\n            object cacheKey, Func<Func<QueryContext, TFunc>> compiler)\r\n        {\r\n            Func<QueryContext, TFunc> compiledQuery;\r\n\r\n            retry:\r\n            if (!_memoryCache.TryGetValue(cacheKey, out compiledQuery))\r\n            {\r\n                if (!_querySyncObjects.TryAdd(cacheKey, null))\r\n                {\r\n                    goto retry;\r\n                }\r\n\r\n                try\r\n                {\r\n                    compiledQuery = compiler();\r\n\r\n                    _memoryCache.Set(cacheKey, compiledQuery);\r\n                }\r\n                finally\r\n                {\r\n                    _querySyncObjects.TryRemove(cacheKey, out _);\r\n                }\r\n            }\r\n\r\n            return compiledQuery;\r\n        }\r\n\r\nProposed Fix:\r\n\r\n        private Func<QueryContext, TFunc> GetOrAddQueryCore<TFunc>(\r\n            object cacheKey, Func<Func<QueryContext, TFunc>> compiler)\r\n        {\r\n            Func<QueryContext, TFunc> compiledQuery;\r\n\r\n            const int maxAttempts = 100;\r\n\r\n            bool syncLock = false;\r\n            int syncLockAttempt = 0;\r\n\r\n            if (!_memoryCache.TryGetValue(cacheKey, out compiledQuery))\r\n            {\r\n                do\r\n                {\r\n                    if (_querySyncObjects.TryAdd(cacheKey, null))\r\n                    {\r\n                        syncLock = true;\r\n                    }\r\n                } while (!syncLock && ++syncLockAttempt < maxAttemptsk);\r\n\r\n                if (syncLock)\r\n                {\r\n                    try\r\n                    {\r\n                        compiledQuery = compiler();\r\n\r\n                        _memoryCache.Set(cacheKey, compiledQuery);\r\n                    }\r\n                    finally\r\n                    {\r\n                        bool syncLockRemoved = false;\r\n                        int syncRemovedAttempt = 0;\r\n\r\n                        do\r\n                        {\r\n                            syncRemoved = _querySyncObjects.TryRemove(cacheKey, out _);\r\n                        }\r\n                        while (!syncLockRemoved && ++syncRemovedAttempt < maxAttempts);\r\n\r\n                        if (!syncLockRemoved)\r\n                        {\r\n                            throw new Exception(\"Failure Removing Syncing Lock. Cache Timeout at MaxAttempts\");\r\n                        }\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    throw new Exception(\"Failure Acquiring Sync Lock. Cache Timeout at MaxAttempts\");\r\n                }\r\n                \r\n            }\r\n\r\n            return compiledQuery;\r\n        }\r\n\r\n"}}
{"actor":{"display_login":"ajcvickers"}}