问题描述
2021年10月,技术部反馈,在操作储量的时候,保存的时候正常,再次打开就会多出一些储量对象。而且这个情况还不一定出现,需要操作多次。
问题排查、重现、分析
技术部人员给录制了一个视频,视频足足有12分钟,视频里面就是在重复(1)粘贴储量对象;(2)双击对象,弹出对话框修改属性,确定;(3)移动该储量对象到合适的位置。
为了确保不是用户误操作(是不是有Ctrl+C),我把视频每一帧跟踪了一遍。同时记录每个储量对象添加的坐标以及包含的主要操作,用来与技术部发过来的图纸进行比较。光是记录这些坐标就用了1个小时。
添加日志后发现,同一个实体有的时候会被添加两次(如sysId为3的对象,第一次添加得到了dbId3,可能紧接着又再次在数据库中添加得到dbId4),确实如技术部人员所说并非每次都会出现,这就让排查工作更加复杂了。但是还好总归是可以重现的。
最终定位到实体的copy中,有些时候正常的dbId(非-1)被重置为-1了,这样导致了这个实体被重复添加了。怀疑可能有多线程导致问题,但是自动保存一直是在onTimer中。打印出来的线程Id也说明了这些动作都是在同一个线程中完成的。
前一天晚上找到23:00没有定位到问题,终于第二天早上发现了这个copy位置的错误,进而也就根据堆栈找到了问题所在。
症结所在
直接原因
用户在粘贴生成一个储量对象(假设代号为C)后,这个时候迅速双击该对象,弹出储量实体的对话框。在储量EPE中代码如下:
1 | BOOL CEntityParser_ChuLiang::ParseCustomPropertyDlg(ILoMapCtrl* pMapCtrl, LONG layerId, LONG entityID, LONG flag, COOR* pPosition) |
由于操作的速度够快,在步骤(1)执行的时候自动保存还没有完成,但是在步骤(2)之前自动保存执行了。即按照(1)->自动保存->(2)这样的步骤来完成实体的操作。由于执行(1)的时候,自动保存还没有执行,所以返回的pEntity中的dbId为-1(表示添加),sysId为3。接下来执行自动保存,自动保存正常执行,在数据库中添加了一个对象,并将dbId=3返回给了数据集缓存中的sysId=3的实体对象。然后执行(2),这一次又将pEntity的错误dbId(-1)传给了数据集缓存,进而把sysId=3的实体的dbId再次设置成了-1,最终下一次的自动保存就会把这个实体重新添加一次(得到dbId-4)。
如果操作的时候动作稍微缓和一点,添加后等待自动保存完成,然后步骤(1)就能得到正常的dbId,这样就不会出错。这也就是为什么有些情况出错,有些情况正常。当然如果动作再快一点,让自动保存在(2)以后执行,数据也会正常。
根源所在
最终问题定位到了UpdateEntity函数,也就是底层的实体更新代码中。外部调用者由于拷贝了个实体出来,他无法保证dbId的实时准确性,而只能保证sysId准确。这时候在EntityByID和UpdateEntity中间,可能这个对象的dbId已经升级了。所以我们要防止更新实体时对合法dbId的修改。
1 | // 外部传进来的dbId可能是旧的 |
同理,在undo处理时,copy操作也要做相应的处理(奇怪的时undo早已经有了相应的代码)。