最新要闻
- 国内外多名大胃王意外死亡 有人胖到320斤有人开播前突然昏迷:专家科普
- 环球微头条丨男子整形后称没法靠颜值吃饭了:丢了工作
- 《暗黑4》公测性能实测:RTX 4090显卡流畅跑8K
- 世界要闻:李彦宏谈文心一言:市场反馈符合预期 股价波动没必要解释
- 焦点滚动:挺能藏啊!男子电动滑板车藏84个SSD入境被海关查获
- 天天微头条丨卡佩罗:那不勒斯和国米将晋级 迈尼昂和奥纳纳是米兰双雄的关键
- 每日观点:曹姓明星收20万带货3月成交278元 被判退还13.9万:要量力而行
- 13代酷睿躺赢了 4nm锐龙7000跳票:此前规格被砍2刀
- 环球观热点:《流浪地球2》门框机器人科幻十足 设计师详解:还能晾衣服能甩干
- 东北首条海下/跨海地铁!大连地铁5号线正式运营
- 动态焦点:暴雪:《暗黑》系列能成功多亏了韩国玩家热情和爱戴
- 全球观点:朱雀二号遥一运载火箭发射失利:已查明飞行故障 通过归零评审
- 全球热头条丨《雷霆沙赞2》豆瓣开分6.5:加朵女神加分、剧情被批幼稚低级
- 张兰被曝国外欠债9.8亿,海外家庭信托被追债,拼命带货疑为还债
- 肯德基全家桶被曝吃出生的炸鸡!店家回应是锅出现故障
- 世界观速讯丨8万元会成爆款吗?宝骏悦也实车曝光:像吉姆尼、能跑303公里
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
一次 Hyperf 注解失效问题分析
问题环境
PHP: 8.0.13Swoole: 4.6.2Hyperf: 2.2.33运行环境: Docker Desktop on WSL2
文章会持续修订,转载请注明来源地址:https://her-cat.com/posts/2023/03/02/hyperf-annotation-failure-problem-analysis/
(资料图片仅供参考)
问题背景
有同事说我之前使用注解实现的某个功能有问题,具体表现就是有部分使用了注解的类没有被 Hyperf 收集到注解收集器中,导致出现了不符合预期的结果。
由于这个功能已经运行了一段时间,并且我在自己的电脑(Mac)上测试是正常的,找另外一个跟他同样使用 Windows + Docker 开发的同事进行测试也是正常的,所以可以排除业务代码和环境的问题。
简化后的代码如下:
#[Attribute(Attribute::TARGET_CLASS)] class CustomAnnotation extends AbstractAnnotation{} #[CustomAnnotation] class Foo{ } #[CustomAnnotation] class Bar{ }
在上面的代码中,定义了一个注解类 CustomAnnotation
,并且在两个类上使用了这个注解。期望的结果是 Foo
和 Bar
都能够被 Hyperf 收集到注解收集器中,但实际上只有 Foo
被收集到了。
Foo 和 Bar 分别在不同的文件中,但是都在同一个目录下,该目录下的文件数量有 60+。
于是我俩开始在他的电脑上排查是不是 Hyperf 的问题。
源码分析
在 Hyperf 启动时, ClassLoader
类加载器会扫描项目中所有的类文件,并将元数据(注解与类之间的关系)收集到相应的注解收集器中,如果没有自定义注解收集器,则默认统一收集到 Hyperf\Di\Annotation\AnnotationCollector
类中。
下面是完成收集注解的主要逻辑:
- 使用 symfony/finder 组件提供的
Finder
类遍历指定目录下所有的 PHP 类文件。 - 通过反射读取每个文件中的类及其属性、方法上使用的注解。
- 依次检查这些注解是否实现了
Hyperf\Di\Annotation\AnnotationInterface
接口,该接口定义了三个方法分别用于收集类、方法、属性的元数据。 - 如果注解实现了该接口,根据注解使用位置调用相应的方法将其收集到注解收集器中。
完成收集后,我们就能使用注解收集器提供的静态方法的获取对应的元数据用于实现一些自定义的逻辑和功能。
第一步就是先检查类文件是否被 Finder
类读取到了,这部分的逻辑在 ReflectionManager::getAllClasses()
静态方法中。
public static function getAllClasses(array $paths): array { $finder = new Finder(); // 设置读取指定目录下的 PHP 文件 $finder->files()->in($paths)->name("*.php"); $parser = new Ast(); $reflectionClasses = []; foreach ($finder as $file) { try { // 解析文件内容获取类名称 $stmts = $parser->parse($file->getContents()); if (! $className = $parser->parseClassByStmts($stmts)) { // 没获取到说明没有定义类 continue; } $reflectionClasses[$className] = static::reflectClass($className); } catch (\Throwable) { } } return $reflectionClasses; }
将获取目录下文件的这段代码提出来单独进行测试。由于 Finder
类实现了 IteratorAggregate
接口,所以在上面的代码中可以直接对 Finder
类进行遍历,也可以使用 iterator_to_array()
函数直接获取迭代器的结果。
$finder = new Finder(); // 设置读取指定目录下的 PHP 文件$finder->files()->in("出现问题的目录路径")->name("*.php"); var_dump(iterator_to_array($finder));
通过观察打印的结果就发现了问题所在:没有读取到 Bar
的类文件。
当时就在想,这么流行的一个组件包总不能出现这么低级的 Bug 吧?抱着怀疑的心态继续分析 Finder
类实现迭代器的代码,最后将问题定位到了 PHP 内置的 RecursiveDirectoryIterator
类上,Finder
类实际上就是对 PHP 的这些类做了一层封装。
RecursiveDirectoryIterator 提供了一个用于递归迭代文件系统目录的功能,用这个类再次进行上面的测试,依然没有读取到 Bar
的类文件。
$iter = new RecursiveDirectoryIterator("出现问题的目录路径");var_dump(iterator_to_array($iter));
于是,我又一次陷入了怀疑中,难道 PHP 实现的这个类有问题?还得继续看 PHP 的源码?我在犹豫了一会后打开了 Google,抱着肯定有人也遇到过这个问题的想法输入了「RecursiveDirectoryIterator bug」,按下回车,在短暂的页面加载后...
嘿,还真有人已经遇到过这个问题。
真相大白
在前几条搜索结果中,赫然发现有人在 PHP 官方的 Bug 系统反馈了这个问题:RecursiveDirectoryIterator returns incorrect results for Docker Desktop on WSL2,并贴心的附带了可以复现问题的代码。
下面是精简过后的复现代码。
$filesPath = __DIR__."/files"; if (! mkdir($filesPath) && ! is_dir($filesPath)) { throw new \RuntimeException(sprintf("Directory "%s" was not created", "files")); } $max = 1; $stop = 5000; // 生成测试文件,模拟目录中文件较多的情况 foreach(range(1, $stop) as $index) { $message = sprintf("creating %s\n", $index); echo $message; file_put_contents(__DIR__ . "/files/file" . $index, str_repeat("A", 100)); } $iter = new \RecursiveDirectoryIterator($filesPath, FilesystemIterator::KEY_AS_PATHNAME|FilesystemIterator::CURRENT_AS_FILEINFO|FilesystemIterator::SKIP_DOTS); var_dump(iterator_count($iter));// 打印出来的数字小于 5000 说明复现成功了
PHP 官方给出了回复:这是 WSL 的 Bug,并提供了相关的 issue:WSL2: Seek of directory entry by lseek does not work on v9fs。里面的实际输出跟我们发现这个问题时的打印结果几乎一模一样,感兴趣的可以去看看。
有人可能会问,lseek()
函数跟 RecursiveDirectoryIterator
类有什么关系吗 ?
当然有!将上面的代码保存到 test.php 文件,然后执行 strace php test.php
命令查看 PHP 代码的系统调用情况。
...省略其他部分...openat(AT_FDCWD, "/home/ubuntu/files", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4fstat(4, {st_mode=S_IFDIR|0775, st_size=135168, ...}) = 0brk(0x55d84733f000) = 0x55d84733f000getdents(4, /* 1024 entries */, 32768) = 32752lseek(4, 0, SEEK_SET) = 0getdents(4, /* 1024 entries */, 32768) = 32752getdents(4, /* 1024 entries */, 32768) = 32768getdents(4, /* 1024 entries */, 32768) = 32768getdents(4, /* 1024 entries */, 32768) = 32768getdents(4, /* 906 entries */, 32768) = 28992getdents(4, /* 0 entries */, 32768) = 0write(1, "int(5000)\n", 10int(5000)) = 10close(3) = 0close(4) = 0...省略其他部分...
可以看到,RecursiveDirectoryIterator
类在底层中调用了 lseek()
函数,它的作用是设置文件偏移量。lseek(4, 0, SEEK_SET)
表示将文件偏移量设置为 0,即文件开头的位置,该函数无法工作会导致下次操作依然使用的是原来的文件偏移量。
Linux 中万物皆为文件,包括目录。
用 PHP 代码来举个例子,这里使用 PHP 的 rewinddir()
函数代替 lseek()
函数,实际上底层调用的还是 lseek()
函数。
$dh = opendir(__DIR__ . "/files"); echo "开始读取目录中的所有文件:" . PHP_EOL; while (($file = readdir($dh)) !== false) { echo "filename:" . $file . PHP_EOL; } echo "再次读取目录中的所有文件:" . PHP_EOL; // 这时文件偏移量已经到达文件的末尾,再次读取目录将不会有任何输出,模拟 lseek() 函数无法工作的情况 while (($file = readdir($dh)) !== false) { echo "filename:" . $file . PHP_EOL; } // 将文件偏移量重置到文件的开头 rewinddir($dh); echo "重置偏移量后读取目录中的所有文件:" . PHP_EOL; // 与第一次读取的结果相同,模拟 lseek() 函数正常工作的情况while (($file = readdir($dh)) !== false) { echo "filename:" . $file . PHP_EOL; } closedir($dh);
在 WSL2 以外的系统中运行以上代码,可以得到与预期一致的结果。那么在 WSL2 中运行的结果是什么?
解决问题
当然,最好是 WSL 官方能够修复这个问题,但是从有人提出这个问题到现在已经快三年了依然没有被解决的情况来看,不知道得等到猴年马月。
提问的作者也给出了一种解决方案,开启 Hyper V。但是经过测试后发现开启 Hyper V 依然会出现这个问题,所以最后直接从 WSL2 回滚到 WSL1,从另一种「根本上」解决这个问题。
总结
等等,文章开头不是说已经排除是环境的问题了吗?怎么最后又是环境的问题了?
是的,这是由于我当时并没有问清楚,只是确认了另一个同事是用 Docker 运行的,我怎么也没想到他是本地运行了个虚拟机,然后在虚拟机里面运行 Docker...
当然,后面的源码分析也不是一点作用都没有,至少将问题的范围从 Hyperf 框架缩小到了 Finder
类,再到 RecursiveDirectoryIterator
类。否则直接 Google 搜索「Hyperf 注解失效」是很难找到正确答案的。
在这篇文章中,讲述了我排查「Hyperf 注解失效」问题的过程,整个排查过程看似一气呵成,但实际上要曲折得多,甚至一度觉得这是个玄学问题。
最后,没有 Bug 的程序是不存在的,不要过度迷信那些看似很可靠的系统。
关键词:
-
环球热推荐:008爬虫之短短20行代码下载周杰伦所有歌曲
今天废话不多说直接上代码。下载周杰伦所有歌曲。 下载周杰伦歌曲importrequestsimportreforiinrange(36):url=f"http:
来源: 环球热推荐:008爬虫之短短20行代码下载周杰伦所有歌曲
一次 Hyperf 注解失效问题分析
全球看热讯:Qt+百度AI文字识别OCR小工具
国内外多名大胃王意外死亡 有人胖到320斤有人开播前突然昏迷:专家科普
热点在线丨2023省选16天
著名的Breach黑客论坛管理员被捕
环球微头条丨男子整形后称没法靠颜值吃饭了:丢了工作
《暗黑4》公测性能实测:RTX 4090显卡流畅跑8K
世界短讯!SSL/TLS协议运行机制的概述
最新资讯:重学c#系列—— explicit、implicit与operator[三十四]
世界要闻:李彦宏谈文心一言:市场反馈符合预期 股价波动没必要解释
焦点滚动:挺能藏啊!男子电动滑板车藏84个SSD入境被海关查获
【天天快播报】webpack原理(2):ES6 module在Webpack中如何Tree-shaking构建
CTF show 信息收集篇
Quicker 快速开发,控制脚本关闭(示例,鼠标连点器)
天天微头条丨卡佩罗:那不勒斯和国米将晋级 迈尼昂和奥纳纳是米兰双雄的关键
每日观点:曹姓明星收20万带货3月成交278元 被判退还13.9万:要量力而行
13代酷睿躺赢了 4nm锐龙7000跳票:此前规格被砍2刀
已知球面经纬度求方位角和反方位角(awk一行代码实现)
环球观热点:《流浪地球2》门框机器人科幻十足 设计师详解:还能晾衣服能甩干
东北首条海下/跨海地铁!大连地铁5号线正式运营
世界热讯:Linux学习笔记
报道:插件化架构设计(2):插件化从设计到实践该考量的问题汇总
【天天新要闻】Vins 前端中高效的去畸变的方式解析
动态焦点:暴雪:《暗黑》系列能成功多亏了韩国玩家热情和爱戴
全球观点:朱雀二号遥一运载火箭发射失利:已查明飞行故障 通过归零评审
全球热头条丨《雷霆沙赞2》豆瓣开分6.5:加朵女神加分、剧情被批幼稚低级
【全球独家】万字血书Vue—Vue的核心概念
张兰被曝国外欠债9.8亿,海外家庭信托被追债,拼命带货疑为还债
Ocelot使用与设置路由Routing
环球速递!arthas排查线上问题真是太好用了!
肯德基全家桶被曝吃出生的炸鸡!店家回应是锅出现故障
世界快播:C++ class struct
环球视点!Windows OpenGL ES 图像 GPUImageLookupFilter
世界观速讯丨8万元会成爆款吗?宝骏悦也实车曝光:像吉姆尼、能跑303公里
每日热文:印度男子因新娘高三成绩不好要求退婚 还要退5千彩礼:网友看笑
世界观焦点:CSS学习笔记
热门看点:女生被拍同学勇敢对峙让男子删除 想保护好自己的朋友:网友称赞勇敢
1.5mm!iPhone 15 Pro Max将打破最薄边框纪录:CAD外观渲染图曝光 更帅了
全球微头条丨没有科技与狠活 :依能天然苏打水2.3元发车 无糖无气0卡
03月18日09时福建漳州疫情数据 阳了以后为什么会腰疼?应该怎么办?
当前要闻:为什么文件删除了但磁盘空间没有释放?
微博图床被废,自己动手丰衣足食。
【聚看点】Source Generator初探
4、AOP
天天亮点!汽车降价潮蔓延!成都豪撒1亿购车补贴 汽车流通协会称武汉汽车降价不公平
【天天热闻】俞敏洪称下辈子宁愿当没钱的流浪汉:自己周围的企业家都在没日没夜的干活
比亚迪出海再下一城!乌兹比克斯坦三车齐发:宋PLUS 22万起售
世界聚焦:调查显示民众预期英国央行将继续加息
【全球速看料】差距有多大?一图看懂蔚来、小鹏、理想汽车2022年第四季度财报:老大变了
每日速读!水晶球档杆绝无仅有!韩系豪华电动车捷尼赛思GV60上市:28.58万起
世界速讯:你对Linux窗口管理程序Tmux了解吗
【全球聚看点】还买什么汉兰达!全新大七座SUV福特锐界L开售:22.98万历史新低
【世界速看料】超市6500元招聘引学生排队投简历 负责人:已收到五十多份
世界看点:南宁市2023年事业单位统一考试简章发布 337个岗位共招1764人
【当前独家】读Java性能权威指南(第2版)笔记20_垃圾回收G
何小鹏:王凤英一周工作七天、让大家很卷
何小鹏谈竞争对手降价:油车一定会反击、小鹏将降低25%生产成本
springboot跨域问题解决方案
天天亮点!听闻索尼PS5 Pro主机明年发售后:老玩家们集体不干了
环球通讯!日本女大胃王菅原初代患肠癌病逝:曾10分钟吞399碗荞麦面
全球热点评!美国一核电站承认150万升核污染水泄漏:已隐瞒数月
导演郭帆都看不下去!众筹1亿的《流浪地球2》周边 为啥要偷工减料?
说句话就能做表格、PPT!微软把GPT-4塞进办公套件 我慌了
每日热门:喉结左侧有个硬疙瘩_左侧睾丸里有个小疙瘩是什么
AIGC的下一站是什么?
速读:vue2前端导出带背景色表格 xlsx xlsx-style
环球新资讯:Attention与SelfAttention
五角大楼官员表示:太阳系中可能存在外星母舰探测地球
环球消息!每日机构分析:3月17日
fiddler:The system proxy was changed.Click to reenable capturing
iPhone 15 Pro Max屏幕边框窄爆:将打破小米13纪录
99元 联想YOGA新款M5无线鼠标上架:鹅卵石设计
张裕葡小萄赤霞珠甜红葡萄酒2支到手39.9元:酒香浓郁
世界热讯:手机百度文库的下载券怎么用啊 为什么(百度文库不能用下载券)
网红店半天妖烤鱼被曝垃圾桶捞回食材上桌!合肥市监局:全市门店停业
天天微头条丨赛博2077支持DLSS3 iGame RTX 40显卡实战:性能2倍提升
河南三月飞雪 突降大雪竟与人工增雨有关
中金所就30年期国债期货合约征求意见
世界观速讯丨铭瑄发布旗舰级MGG RTX 4080、4070 Ti:丧心病狂5风扇、9热管
【焦点热闻】AMD份额涨不动了 专家称Intel的麻烦已结束:CPU竞争力更强
每日视点!山东一公司疑设卑劣人员从业跟踪岗:你去哪我就发函到哪
微头条丨铲屎官注意!研究表明养宠物或影响睡眠
热点评!你买过大船货吗?男子电动滑板车藏84个SSD入境被海关查获
全球新消息丨如何在Docker下部署nacos并注册Java服务
每日讯息!记录--vue中封装一个右键菜单组件(复制粘贴即可使用)
【新要闻】一汽奔腾销量惨淡,靠预售23.58万起的奔腾M9有望逆转吗?
天天快资讯:车主注意:新一轮国内油价降了!加满一箱油将少花4元
焦点要闻:全球最大集装箱船将在宁波舟山港开启首航:比航母还长
今日热讯:接二连三胜!长三乙火箭成功发射高分十三号02星
环球快资讯:《三体》动画播放量破5亿!豆瓣评分暴跌至3.9 差评率高达86%
票房已突破45亿!《满江红》宣布密钥再次延期
全球热门:“315”曝光:带货直播间水军泛滥,该如何应对?
天天观速讯丨债市日报:3月17日
消防车被小车挡道 业主联系未果合力掀翻!车主不干了
滚动:让越野车无路可走!极氪001成功挑战原路虎越野基地
世界热门:座椅加热1299元!smart回应硬件订阅:预埋硬件算送的 成本没进卖车价
环球快播:何炅再提《快乐大本营》:引发网友感慨
全球实时:1个案例读懂——游戏产品如何用 A/B 测试做增长
当前消息!Tailwind CSS 备忘清单_开发速查表分享