{"action":"created","issue":{"url":"https://api.github.com/repos/Starcounter/Starcounter/issues/3432","repository_url":"https://api.github.com/repos/Starcounter/Starcounter","labels_url":"https://api.github.com/repos/Starcounter/Starcounter/issues/3432/labels{/name}","comments_url":"https://api.github.com/repos/Starcounter/Starcounter/issues/3432/comments","events_url":"https://api.github.com/repos/Starcounter/Starcounter/issues/3432/events","html_url":"https://github.com/Starcounter/Starcounter/issues/3432","id":138922293,"number":3432,"title":"DbHelper.FromID not updated,  DbAccessViolation","user":{"login":"careri","id":4263597,"avatar_url":"https://avatars.githubusercontent.com/u/4263597?v=3","gravatar_id":"","url":"https://api.github.com/users/careri","html_url":"https://github.com/careri","followers_url":"https://api.github.com/users/careri/followers","following_url":"https://api.github.com/users/careri/following{/other_user}","gists_url":"https://api.github.com/users/careri/gists{/gist_id}","starred_url":"https://api.github.com/users/careri/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/careri/subscriptions","organizations_url":"https://api.github.com/users/careri/orgs","repos_url":"https://api.github.com/users/careri/repos","events_url":"https://api.github.com/users/careri/events{/privacy}","received_events_url":"https://api.github.com/users/careri/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":20,"created_at":"2016-03-07T08:50:27Z","updated_at":"2016-03-09T13:15:09Z","closed_at":null,"body":"Hi!\r\n\r\nI've written code that ensures that an instance of e.g. Person exists with the name Carl. Simplified steps:\r\n1. Search for the instance on the current thread\r\n2. If it doesn't exist switch to another attached thread and create the instance in Db.Transact.\r\n3. Handle constraint violations and reread the instance from db if someone else created it at the same time.\r\n4. Reload the instance on the main thread using DbHelper.FromID. The reason for reloading is that I got DbAccessViolation if using the created instance directly. So the main thread sometimes can't access and read the created and commited instance.\r\n\r\n99.99% of the time this logic works just fine. But sometimes step4 fails. I'm using minimal transacts.\r\nThere seems to be a slight delay in the DbHelper.FromIDs index? Because if I uncomment: ```//NamedLog(\"Created OID: {0}\", oid.Value);```\r\n before returning to the main thread in step4 it's 100% accurate.\r\n\r\nIt took me quite a while to reproduce my production code in a simplified example, still not simple...\r\n```\r\nusing System;\r\nusing Starcounter;\r\nusing System.Text;\r\nusing System.Threading.Tasks;\r\nusing System.Diagnostics;\r\nusing System.Threading;\r\n\r\nnamespace EnsureTest\r\n{\r\n\r\n    class Program\r\n    {\r\n        public static DbSession Singleton = new DbSession();\r\n        private static bool s_failed;\r\n\r\n        static void Main()\r\n        {\r\n            Item.CreateIndex();\r\n            Item.DeleteAll();\r\n\r\n            while (!s_failed)\r\n            {\r\n                Parallel.For(0, 100, CreateInstance); \r\n            }\r\n        }\r\n\r\n        private static void CreateInstance(int i)\r\n        {\r\n            if (s_failed)\r\n            {\r\n                Log(\"ParallelJob: {0}, skipped failed flag set\", i);\r\n                return;\r\n            }\r\n            Singleton.RunSync(() =>\r\n                {\r\n                    Log(\"ParallelJob: {0}\", i);\r\n                    // Should create the instance\r\n                    var helper = new EnsureHelper(\"Carl-\" + i + \"-\" + DateTime.Now);\r\n                    Item item = helper.Ensure();\r\n                    Log(helper.ToString());\r\n\r\n                    // Should read the instance\r\n                    item = helper.Ensure();\r\n                    Log(helper.ToString());\r\n                }, SchedulerPartitioner.GetNext());\r\n        }\r\n\r\n        private static void Log(string msg, params object[] args)\r\n        {\r\n            var timestamp = DateTime.Now.ToLongTimeString();\r\n            var tid = \"[\" + Thread.CurrentThread.ManagedThreadId + \"] \";\r\n            var timeMsg = \"[\" + timestamp + \"]\" + tid + msg;\r\n            Debug.Print(timeMsg, args);\r\n            Console.WriteLine(timeMsg, args);\r\n        }\r\n\r\n        private class EnsureHelper\r\n        {\r\n            private readonly string m_name;\r\n            private readonly string m_logPrefix;\r\n            private bool m_isCreated;\r\n            private Item m_instance;\r\n            \r\n\r\n            public EnsureHelper(string name)\r\n            {\r\n                m_name = name;\r\n                m_logPrefix = string.Format(\"[{0}]\", m_name);\r\n            }\r\n\r\n            private void NamedLog(string msg, params object[] args)\r\n            {\r\n                Log(m_logPrefix + msg, args);\r\n            }\r\n\r\n            public Item Ensure()\r\n            {\r\n                m_instance = null;\r\n                m_isCreated = false;\r\n\r\n                m_instance = ReadInstance();\r\n\r\n                if (m_instance == null)\r\n                {\r\n                    m_instance = Create();\r\n                }\r\n\r\n                return m_instance;\r\n            }\r\n\r\n            private Item ReadInstance()\r\n            {\r\n                return Db.SQL<Item>(\"SELECT i FROM EnsureTest.Item i WHERE i.Name=?\", m_name).First;\r\n            }\r\n\r\n            private Item Create()\r\n            {\r\n                Item instance = null;\r\n\r\n                for (int i = 0; i < 100; i++)\r\n                {\r\n                    NamedLog(\"Create Attempt: {0}/{1}\", i, 100);\r\n                    try\r\n                    {\r\n                        // Executes in another thread and transact\r\n                        ulong? oid = DoCreate();\r\n\r\n                        if (oid.HasValue)\r\n                        {\r\n                            // Reload the instance using object id\r\n                            instance = (Item)DbHelper.FromID(oid.Value);\r\n\r\n                            if (instance != null)\r\n                            {\r\n                                m_isCreated = true;\r\n                                break;\r\n                            }\r\n                            else\r\n                            {\r\n                                NamedLog(\"Failed to retrieve newly created instance: {0}\", oid.Value);\r\n                            }\r\n                        }\r\n                        // Try reading \r\n                        instance = ReadInstance();\r\n\r\n                        if (instance != null)\r\n                        {\r\n                            break;\r\n                        }\r\n                    }\r\n                    catch (Exception ex)\r\n                    {\r\n                        s_failed = true;\r\n                        instance = ReadInstance();\r\n\r\n                        if (instance != null)\r\n                        {\r\n                            break;\r\n                        }\r\n                        NamedLog(\"Error: \" + ex);\r\n                    }\r\n                }\r\n                return instance;\r\n            }\r\n\r\n            private ulong? DoCreate()\r\n            {\r\n                ulong? oid = null;\r\n\r\n                var tid = Thread.CurrentThread.ManagedThreadId;\r\n                // Run as task to change thread\r\n                Task.Factory.StartNew(() =>\r\n                    {\r\n                        Singleton.RunSync(() =>\r\n                            {\r\n                                try\r\n                                {\r\n                                    Db.Transact(() =>\r\n                                        {\r\n                                            var tid2 = Thread.CurrentThread.ManagedThreadId;\r\n\r\n                                            if (tid == tid2)\r\n                                            {\r\n                                                throw new ApplicationException(\"No running on new thread\");\r\n                                            }\r\n\r\n                                            var instance = new Item(m_name);\r\n                                            oid = instance.GetObjectNo();\r\n                                        });\r\n                                    // If I uncomment this log DbHelper will always find the instance on the other thread.\r\n                                    // Why is that, some context switching that takes enough time for the DbHelper to be updated?\r\n                                    //NamedLog(\"Created OID: {0}\", oid.Value);\r\n                                }\r\n                                catch (Exception ex)\r\n                                {\r\n                                    s_failed = true;\r\n                                    NamedLog(\"OID: {0}{1}{2}\", oid.Value, Environment.NewLine, ex);\r\n                                    oid = null;\r\n                                }\r\n                            });\r\n                    }).Wait();\r\n\r\n                return oid;\r\n            }\r\n\r\n            public override string ToString()\r\n            {\r\n                return string.Format(\"[{0}], OID: {1}, IsCreated:{2}\", m_name, m_instance.GetObjectNo(), m_isCreated);\r\n            }\r\n        }\r\n\r\n\r\n    }\r\n\r\n    [Database]\r\n    public class Item\r\n    {\r\n        public Item(string name)\r\n        {\r\n            Name = name;\r\n        }\r\n\r\n        public readonly string Name;\r\n\r\n        internal static void CreateIndex()\r\n        {\r\n            try\r\n            {\r\n                using (var t = new Transaction(false))\r\n                {\r\n                    Db.SQL(\"CREATE UNIQUE INDEX uname ON EnsureTest.Item (Name ASC)\");\r\n                    t.Commit();\r\n                }\r\n            }\r\n            catch\r\n            {\r\n                // If already created\r\n            }\r\n        }\r\n\r\n        internal static void DeleteAll()\r\n        {\r\n            foreach (var item in Db.SQL<Item>(\"SELECT i FROM EnsureTest.Item i\"))\r\n            {\r\n                Db.Transact(item.Delete);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// <summary>\r\n    /// Distributes over the schedulers using round robin\r\n    /// </summary>\r\n    public class SchedulerPartitioner\r\n    {\r\n        private static readonly byte s_maxId = Db.Environment.SchedulerCount;\r\n        private static int s_schedulerId;\r\n\r\n        public static byte GetNext()\r\n        {\r\n            int expected = 0, current = 0;\r\n            do\r\n            {\r\n                expected = (byte)(s_schedulerId + 1);\r\n                current = s_schedulerId;\r\n\r\n                if (expected == s_maxId)\r\n                {\r\n                    expected = 0;\r\n                }\r\n\r\n            } while (Interlocked.CompareExchange(ref s_schedulerId, expected, current) != current);\r\n\r\n            return (byte)expected;\r\n        }\r\n    }\r\n}\r\n```"},"comment":{"url":"https://api.github.com/repos/Starcounter/Starcounter/issues/comments/194292588","html_url":"https://github.com/Starcounter/Starcounter/issues/3432#issuecomment-194292588","issue_url":"https://api.github.com/repos/Starcounter/Starcounter/issues/3432","id":194292588,"user":{"login":"eriohl","id":3438751,"avatar_url":"https://avatars.githubusercontent.com/u/3438751?v=3","gravatar_id":"","url":"https://api.github.com/users/eriohl","html_url":"https://github.com/eriohl","followers_url":"https://api.github.com/users/eriohl/followers","following_url":"https://api.github.com/users/eriohl/following{/other_user}","gists_url":"https://api.github.com/users/eriohl/gists{/gist_id}","starred_url":"https://api.github.com/users/eriohl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eriohl/subscriptions","organizations_url":"https://api.github.com/users/eriohl/orgs","repos_url":"https://api.github.com/users/eriohl/repos","events_url":"https://api.github.com/users/eriohl/events{/privacy}","received_events_url":"https://api.github.com/users/eriohl/received_events","type":"User","site_admin":false},"created_at":"2016-03-09T13:15:09Z","updated_at":"2016-03-09T13:15:09Z","body":"> > > Actually that last statement from me is why I change threads. I want to ensure that my object is fully commited and not meta-committed due to the fact that I might have a Db.Transact somewhere in the callstack\r\n\r\n> > But of course. This doesn't solve this problem.\r\n\r\n> What do you mean by this statement? Is there a flaw in my design?\r\n\r\nWhat I mean is that if you may have a Db.Transact in your callstack then Db.Transact won't create a new transaction. So it your design of rescheduling tasks on a different scheduler is because you might have a Db.Transact in your callstack then doing this won't work.\r\n\r\nWhether there is a flaw in your design or not I can not advice on as mentioned. This with mixing long-running (including the implicit read-only transaction) and short running ones falls outside my area of expertise.\r\n\r\n> Well, I want to use the implicit read-only transactaction. I design my application to run on the implicit read-only transaction. Only switching to a Db.Transact when I do writes. The writes are really small tasks without any external IO (logging) as suggested by you @eriohl.\r\n\r\nI'm sorry but there seems to be some misunderstanding. I generally do not advice on application design because as mentioned: I don't know how this stuff is supposed to be used. Nor do I do so here as I tried to be clear about. Again sorry if there has been some misunderstanding on this. I only involved my self in this because you mentioned there was a access violation (which implies a kernel bug)."}}