attempt to yield across metamethod/C-call boundary

博客讲述了在Lua5.1环境中遇到的协程执行时因C调用边界引发的错误。作者通过分析错误原因,发现是由于在C函数(xpcall)内部调用了含有yield的lua函数导致。为解决此问题,提供了两种方案:一是通过打补丁重新编译lua库;二是避免在xpcall内部使用yield。作者实现了第二种方案,并强调了代码设计的注意事项和潜在风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自己项目用到协程,但是里面报错的话,会导致协程撕死掉。导致后面刷的log不是很直观,所以得想个办法处理一下

描述

  • 环境:lua 5.1

尝试

自然的就想到用pcall/xpcall来执行了,大概代码如下:

function LItemMgr:_CreateCoroutineTask(fnTask, ...)
    if type(fnTask) ~= "function" then
        return;
    end

    local co = coroutine.create(function (...)
		local tbParams = {...};
		while true do
		    local status , bFinished = xpcall(function ()
		        return fnTask(unpack(tbParams));
		    end, debug.traceback); 
            tbParams = {coroutine.yield(bFinished)};
		end
	end)

    return co;
end

就是把我想要在协程里面执行的Function加一个保护壳运行,结果函数内不报错的情况下就出错了

attempt to yield across metamethod/C-call boundary

首次看到这个,很纳闷,协程里面yield和C代码有啥关系?因为我这里并没有我导出的C++接口到lua

原因

参考[1]里面解释的很清楚了

Internally, Lua uses the C longjmp facility to yield a coroutine. Therefore, if a function foo calls an API function and this API function yields (directly or indirectly by calling another function that yields), Lua cannot return to foo any more, because the longjmp removes its frame from the C stack.

简单来说,等于是没办恢复c的栈了,就报这个错误了。对于我的情况如下:

  • 传进来的fnTask这个函数里面有yield,因为我打算分帧执行某些操作
  • 但是,xpcall保护运行fnTask的时候,等于是我调用了一个C函数(xpcall等于是API,C语言实现的)
  • 那么,我的情况等于是从C函数调用lua(fnTask),而这个lua函数里面有yield操作
  • 所以,yield的时候,等于是把C的堆栈给干掉了。下次resume的时候,恢复不了堆栈所以抛出错误

解决

参考[3]

方法一:打补丁

参考[4]

我是用的vs编的lib,大概步骤:

  • 1.把lua-5.1.5-coco-1.1.9.patch下载下来
  • 2.因为这个patch需要目录是lua-5.1.5,我直接从参考[4]里面另外下载了一个lua-5.1.5
  • 3.右键这个文件->git ->review/apply single patch…,全部应用
  • 4.会得到一个lua-5.1.5-coco文件夹和源码lua-5.1.5同级
  • 5.把lua-5.1.5-coco/src的全部代码覆盖到lua-5.1.5/src中,这里多了连个文件:lcoco.h和lcoco.c
  • 6.vs新建一个工程,把lua的源码添加进去,包括上面两个多的文件
  • 7.这样新编译出来的lib和dll替代lua-5.1.5源码的lib和dll
  • 8.测试相同的代码,不报错了,大功告成!

方法二:避免yield即可

我仔细想了一下,只要自己xpcall里面不要有yield即可。那么简单来说就是,我在fnTask里面保护运行部分代码(xpcall),只要xpcall里面没有yield就行了。

function LItemMgr:TickBlockByCoroutine(nIndex, nStart, bFrame)
    local nCount = 0;
    local fnToDo = function ()
        local nBlockID, uuid;
        local tbItemInfo = self.ItemList[nIndex];
        if tbItemInfo then
            nBlockID, uuid = unpack(tbItemInfo);
            nCount = nCount + 1;
        end
        
        print("Real Do Something: ", nIndex, nBlockID, uuid)
        -- a.b = 5;
    end
    -- fnToDo();
    local bRet, szErr = xpcall(fnToDo, debug.traceback)
    print("xpcall invoke result: ", bRet, szErr);
    
    local COUNT_PER_FRAME = 2;
    if bFrame and (nStart + nCount) % COUNT_PER_FRAME == 0 then
        _, bFrame = coroutine.yield(false)
        print("bNewFrame = ", bFrame)
    end

	return nCount;
end

简单的一个实现,把真正要做的代码(fnToDo)用xpcall包一下。其他代码其实不会出什么错,而且也是协程分帧必备代码,就可以省掉了。

小结:

  • 可行。
  • 其实,只要把核心代码,或者觉得明显有错误的代码用xpcall保护运行即可
  • 缺点:这样的代码设计很隐晦,其他人很容易改崩掉,所以得加好注释!

参考

[1][4.7 – Handling Yields in C](http://www.lua.org/manual/5.2/manual.html#4.7)

[2][如何在Lua5.1.4中实现这样的效果](http://bbs.chinaunix.net/forum.php?mod=viewthread&action=printable&tid=4065715)

[3][attempt_ to yield across metamethod/C-call _boundary](https://www.cnblogs.com/lightsong/p/5785615.html)

[4][Coco — True C Coroutines for Lua](http://coco.luajit.org/)

<think>好的,我现在要帮用户解决Tomcat 9.0.68启动时因为找不到javax.servlet.ServletContext.getVirtualServerName方法导致应用无法启动的问题。首先,我需要分析这个问题出现的原因。 用户提到了ServletContext类中缺少getVirtualServerName方法,而且提到了servlet-api-2.5和tomcat-embed-core-9.0.68版本不一致。这可能是因为项目依赖的Servlet API版本过低,而Tomcat 9需要更高版本的Servlet API。getVirtualServerName方法是Servlet 3.1规范中引入的,所以如果项目还在使用servlet-api-2.5,显然不兼容Tomcat 9,因为Tomcat 9支持的是Servlet 4.0规范。 接下来,我需要考虑解决方案。首先应该检查项目的依赖配置,确保所有的Servlet API相关的jar包都是与Tomcat 9兼容的版本。比如,Maven项目中可能有对javax.servlet:servlet-api的依赖,版本可能被指定为2.5,这时候需要升级到3.1或更高版本。同时,要注意Tomcat 9自带的servlet-api.jar版本是否正确,避免其他依赖冲突覆盖了正确的版本。 另外,用户提到的tomcat-embed-core-9.0.68可能来自Spring Boot的嵌入式Tomcat,这时候需要确认Spring Boot的版本是否与Tomcat 9兼容。例如,Spring Boot 2.x系列通常对应Tomcat 9,而如果使用旧版的Spring Boot可能引用了不兼容的Tomcat版本,需要升级Spring Boot到合适的版本。 还有可能需要排除传递依赖中的旧版servlet-api。比如,在Maven中,某些库可能引入了旧版本的servlet-api,这时候需要用<exclusion>标签排除掉,确保项目中只使用正确版本的API。 最后,清理构建工具中的缓存,重新编译项目,确保所有依赖都更新正确。启动Tomcat后,问题应该解决。如果还有问题,可能需要检查其他依赖是否有冲突,或者应用代码中是否有地方直接引用了旧版的类或方法。 总结起来,解决方案的关键步骤是:升级Servlet API到3.1+,检查并排除旧版依赖,确保构建配置正确,清理并重新构建项目。</think>### 问题分析 Tomcat 9.0.68启动失败的根本原因是**Servlet API版本不兼容**。错误`getVirtualServerName`是Servlet 3.1规范新增的方法[^1],而项目中可能错误地引用了旧版`servlet-api-2.5`(仅支持Servlet 2.5规范),导致类加载器无法找到该方法。 --- ### 逐步解决方案 #### 1. 确认依赖冲突 通过构建工具检查依赖树: - **Maven**命令: ```bash mvn dependency:tree -Dincludes=javax.servlet:* ``` - **Gradle**命令: ```bash gradle dependencies | grep 'servlet-api' ``` 若输出包含`servlet-api-2.5`,则需升级或排除旧依赖。 --- #### 2. 升级Servlet API版本 在构建配置中显式声明Servlet 4.0+依赖: ```xml <!-- Maven示例 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> ``` ```groovy // Gradle示例 implementation 'javax.servlet:javax.servlet-api:4.0.1' // 作用域设为compileOnly ``` --- #### 3. 排除旧版依赖 若第三方库引入旧版Servlet API: ```xml <!-- Maven排除示例 --> <dependency> <groupId>problematic-group</groupId> <artifactId>problematic-artifact</artifactId> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> </exclusion> </exclusions> </dependency> ``` --- #### 4. 验证Tomcat Embed库 若使用嵌入式Tomcat(如Spring Boot): ```xml <!-- 确保tomcat-embed-core版本匹配 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>9.0.68</version> </dependency> ``` --- #### 5. 清理并重建项目 ```bash mvn clean install # Maven gradle clean build # Gradle ``` --- ### 附加验证 通过代码验证Servlet API版本: ```java System.out.println("ServletContext版本: " + getServletContext().getMajorVersion() + "." + getServletContext().getMinorVersion()); ``` 若输出为`3.1`或更高,则配置成功。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值