xLua: try to dispose a LuaEnv with C# callback!

该错误主要是在调用LuaEnv.Dispose()时引起,一般用于游戏重启时将整个lua虚拟机重置.
报错的主要原因有:

  1. 在同一个方法中释放了delegate的引用,但又立即调用了LuaEnv.Dispose().
  2. 没有在C#侧释放标记有[CSharpCallLua]的delegate(赋值为null).

详细说明可以在xLua自带的FAQ(xLua/Doc/faq.md)中找到.

  • 对于第1个问题, 可以将LuaEnv.Dispose()的释放代码单独放到另一个方法中, 参考 https://github.com/Tencent/xLua/issues/634

  • 对于第2个问题:
    xlua提供了一个lua方法(该方法位于XLua/Resources/xlua/util.lua.txt中): print_func_ref_by_csharp()来打印所有通过delegate注入到C#中的lua方法. 通过得到的信息可以依次检查lua往C#中的哪些地方注入了方法,这样就在C#中去把那些delegate类型的变量赋值为null进行释放了.

但依然难免出现疏忽的情况,且当大量使用C#调用lua这种逻辑时,排查问题会更麻烦,所以如果想在报错时确切的知道到底是lua的哪个调用处没有释放,就需要对xLua源码进行额外的处理(v2.1.13测试没问题).

报错的地方是在LuaEnv.cs的Dispose(bool dispose)方法中抛出的, 如下图:


image.png

该处又是调用了translator.AllDelegateBridgeReleased()来得到的检查结果, translator是ObjectTranslator.cs

在生成的各种wrap文件中,对于delegate的获取处理有如下类似代码:

1
2
3
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
XXClass gen_to_be_invoked = (XXClass)translator.FastGetCSObj(L, 1);
gen_to_be_invoked.DelegateHandler = translator.GetDelegate<XXClass.DelegateType>(L, 2);

translator依然是ObjectTranslator,其GetDelegate()定义如下图:


image.png

对于delegate类型的lua传入肯定是个function,所以一定会执行到红圈处的代码,即CreateDelegateBridge().
修改逻辑如下:

  1. 在CreateDelegateBridge()时获取delegate的包装对象WeakReference; 同时获取lua处的调用堆栈信息,即通过lua的debug.getinfo()来得到lua传入function到C#时的文件和代码行号. 将这2个信息保存起来.
  2. 在ReleaseLuaBase()中移除保存的信息,该方法被LuaEnv.Tick()调用.
  3. 在AllDelegateBridgeReleased()中,当检查到某个delegate没有释放时,就打印其WeakReference(在第1步中)关联保存的lua信息,即可精确得到具体是哪里没有释放.

具体修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
        Dictionary<int, WeakReference> delegate_bridges = new Dictionary<int, WeakReference>();

#if UNITY_EDITOR
        // 保存weakReference的hash
        private List<int> bridgeLuaRefs = new List<int>();
        // 保存对应的lua stack info
        private List<string> bridgeLuaRefs2 = new List<string>();
        // 开关
        public bool debugDelegateBridgeRelease = false;
#endif

        public object CreateDelegateBridge(RealStatePtr L, Type delegateType, int idx)
        {
            WeakReference weakRef = null;
#if UNITY_EDITOR
            object obj;
            if (this.debugDelegateBridgeRelease)
            {
                string luaStack = this._GetLuaStack(L);
                obj = this._CreateDelegateBridge(L, delegateType, idx, out weakRef);
                if (weakRef != null)
                {
                    bridgeLuaRefs.Add(weakRef.GetHashCode());
                    bridgeLuaRefs2.Add(luaStack);
                }
            }
            else
            {
                obj = this._CreateDelegateBridge(L, delegateType, idx, out weakRef);
            }
            return obj;
#else
            return this._CreateDelegateBridge(L, delegateType, idx, out weakRef);
#endif
        }

#if UNITY_EDITOR
        private string _GetLuaStack(RealStatePtr L)
        {
            var oldTop = LuaAPI.lua_gettop(L);

            // @see https://www.lua.org/pil/25.2.html
            int debug = LuaAPI.xlua_getglobal(L, "debug");
            LuaAPI.xlua_pushasciistring(L, "getinfo");
            LuaAPI.xlua_pgettable(L, -2); // 获取getinfo function
            LuaAPI.xlua_pushinteger(L, 2); // 传入getinfo()第1个参数
            LuaAPI.xlua_pushasciistring(L, "Sl"); // 传入getinfo()第2个参数
            var strIndex = LuaAPI.lua_pcall(L, 2, 1, 0); // 此时在栈上的结果是一个table
            // @see https://www.lua.org/pil/25.1.html
            LuaAPI.lua_pushstring(L, "source"); // 要获取一个table某个key下的值, 要先传入key的值='source'
            LuaAPI.xlua_pgettable(L, -2); // 获取table[key]返回的值,此时是一个string
            var luasource = LuaAPI.lua_tostring(L, -1);
            LuaAPI.lua_pop(L, 1); // 弹出结果

            LuaAPI.lua_pushstring(L, "currentline");
            LuaAPI.xlua_pgettable(L, -2);
            var currentline = LuaAPI.lua_tostring(L, -1);
            LuaAPI.lua_pop(L, 1);
            LuaAPI.lua_settop(L, oldTop);

            string luaStack = luasource + ":" + currentline;
            return luaStack;
        }
#endif

        private object _CreateDelegateBridge(RealStatePtr L, Type delegateType, int idx, out WeakReference weakRef)
        {

            LuaAPI.lua_pushvalue(L, idx);
            LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
            if (!LuaAPI.lua_isnil(L, -1))
            {
                int referenced = LuaAPI.xlua_tointeger(L, -1);
                LuaAPI.lua_pop(L, 1);

                weakRef = delegate_bridges[referenced];
                if (weakRef.IsAlive)
                {
                    if (delegateType == null)
                    {
                        return weakRef.Target;
                    }
                    DelegateBridgeBase exist_bridge = weakRef.Target as DelegateBridgeBase;
                    Delegate exist_delegate;
                    if (exist_bridge.TryGetDelegate(delegateType, out exist_delegate))
                    {
                        return exist_delegate;
                    }
                    else
                    {
                        exist_delegate = getDelegate(exist_bridge, delegateType);
                        exist_bridge.AddDelegate(delegateType, exist_delegate);
                        return exist_delegate;
                    }
                }
            }
            else
            {
                LuaAPI.lua_pop(L, 1);
            }

            LuaAPI.lua_pushvalue(L, idx);
            int reference = LuaAPI.luaL_ref(L);
            LuaAPI.lua_pushvalue(L, idx);
            LuaAPI.lua_pushnumber(L, reference);
            LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
            DelegateBridgeBase bridge;
            try
            {
#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0
                if (!DelegateBridge.Gen_Flag)
                {
                    bridge = Activator.CreateInstance(delegate_birdge_type, new object[] { reference, luaEnv }) as DelegateBridgeBase;
                }
                else
#endif
                {
                    bridge = new DelegateBridge(reference, luaEnv);
                }
            }
            catch (Exception e)
            {
                LuaAPI.lua_pushvalue(L, idx);
                LuaAPI.lua_pushnil(L);
                LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
                LuaAPI.lua_pushnil(L);
                LuaAPI.xlua_rawseti(L, LuaIndexes.LUA_REGISTRYINDEX, reference);
                throw e;
            }
            if (delegateType == null)
            {
                weakRef = new WeakReference(bridge);
                delegate_bridges[reference] = weakRef;
                return bridge;
            }
            try
            {
                var ret = getDelegate(bridge, delegateType);
                bridge.AddDelegate(delegateType, ret);
                weakRef = new WeakReference(bridge);
                delegate_bridges[reference] = weakRef;
                return ret;
            }
            catch (Exception e)
            {
                bridge.Dispose();
                throw e;
            }
        }

        public bool AllDelegateBridgeReleased()
        {
            var _bridgeLuaRefs = this.bridgeLuaRefs;
            var _bridgeLuaRefs2 = this.bridgeLuaRefs2;
            this.bridgeLuaRefs = new List<int>();
            this.bridgeLuaRefs2 = new List<string>();
            foreach (var kv in delegate_bridges)
            {
                if (kv.Value.IsAlive)
                {
#if UNITY_EDITOR
                    if (this.debugDelegateBridgeRelease)
                    {
                        var hash = kv.Value.GetHashCode();
                        for (int i = 0; i < _bridgeLuaRefs.Count; i++)
                        {
                            if (_bridgeLuaRefs[i] == hash)
                            {
                                UnityEngine.Debug.Log("未释放lua引用: " + _bridgeLuaRefs2.Count + " : " + _bridgeLuaRefs2[i]);
                                break;
                            }
                        }
                    }
#endif
                    return false;
                }
            }
            return true;
        }

        public void ReleaseLuaBase(RealStatePtr L, int reference, bool is_delegate)
        {
            if (is_delegate)
            {
                LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, reference);
                if (LuaAPI.lua_isnil(L, -1))
                {
                    LuaAPI.lua_pop(L, 1);
                }
                else
                {
                    LuaAPI.lua_pushvalue(L, -1);
                    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
                    if (LuaAPI.lua_type(L, -1) == LuaTypes.LUA_TNUMBER && LuaAPI.xlua_tointeger(L, -1) == reference) //
                    {
                        //UnityEngine.Debug.LogWarning("release delegate ref = " + luaReference);
                        LuaAPI.lua_pop(L, 1);// pop LUA_REGISTRYINDEX[func]
                        LuaAPI.lua_pushnil(L);
                        LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX); // LUA_REGISTRYINDEX[func] = nil
                    }
                    else //another Delegate ref the function before the GC tick
                    {
                        LuaAPI.lua_pop(L, 2); // pop LUA_REGISTRYINDEX[func] & func
                    }
                }

                LuaAPI.lua_unref(L, reference);

                WeakReference weakRef = null;
                delegate_bridges.TryGetValue(reference, out weakRef);
                delegate_bridges.Remove(reference);

#if UNITY_EDITOR
                if (this.debugDelegateBridgeRelease && weakRef != null)
                {
                    var hash = weakRef.GetHashCode();
                    for (int i = 0; i < this.bridgeLuaRefs.Count; i++)
                    {
                        if (bridgeLuaRefs[i] == hash)
                        {
                            bridgeLuaRefs.RemoveAt(i);
                            bridgeLuaRefs2.RemoveAt(i);
                            break;
                        }
                    }
                }
#endif
            }
            else
            {
                LuaAPI.lua_unref(L, reference);
            }
        }

在使用new LuaEnv()的地方设置luaEnv.translator.debugDelegateBridgeRelease = true.

转载请注明出处: https://www.jianshu.com/p/58d20d46560a