OWenT's blog
  • Introduction
  • About Me
  • 2020
    • 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等)
    • xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等)
    • 在游戏服务器中使用分布式事务
    • libcopp接入C++20 Coroutine和一些过渡期的设计
    • libatbus 的大幅优化
    • nftables初体验
    • 容器配置开发环境小计
  • 2019
    • PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记
    • 跨平台协程库 - libcopp 简介
    • C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比)
    • 尝鲜Github Action
    • 一些xresloader(转表工具)的改进
    • protobuf、flatbuffer、msgpack 针对小数据包的简单对比
    • 协程框架(libcopp) 小幅优化
    • Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持
    • Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记
    • C++20 Coroutine
    • libcopp merge boost.context 1.69.0
    • Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感
    • Rust玩具-企业微信机器人通用服务
  • 2018
    • 使用ELK辅助监控开发测试环境服务质量和问题定位
    • Webpack+vue+boostrap+ejs构建Web版GM工具
    • 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现
    • Rust的第二次接触-写个小服务器程序
    • 理解和适配AEAD加密套件
    • atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化
    • 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比
    • 可执行文件压缩
    • 初识Rust
    • 使用restructedtext编写xresloader文档
    • atframework的etcd模块化重构
    • C++的backtrace
  • 2017
    • ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装
    • protobuf-net的动态Message实现
    • pbc的proto3接入
    • atgateway内置协议流程优化-加密、算法协商和ECDH
    • 整理一波软件源镜像同步工具+DevOps工具
    • Blog切换到Hugo
    • libcopp v2的第一波优化完成
    • libcopp(v2) vs goroutine性能测试
    • libcopp的线程安全、栈池和merge boost.context 1.64.0
    • GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本
    • libatbus的几个藏得很深的bug
    • 用cmake交叉编译到iOS和Android
    • 开源项目得一些小维护
    • atapp的c binding和c#适配
    • 对象路由系统设计
    • 2016年总结
    • 近期的一个协程流程BUG
  • 2016
    • 重写了llvm+clang+libc++和libc++abi的构建脚本
    • atsf4g完整游戏工程示例
    • atframework基本框架已经完成
    • 游戏服务器的不停服更新
    • 对atbus的小数据包的优化
    • Android和IOS的TLS问题
    • pbc的一个陈年老BUG
    • boost.context-1.61版本的设计模型变化
    • 接入letsencrypt+全面启用HTTP/2
    • 理解Raft算法
    • libatbus基本功能及单元测试终于写完啦
    • 博客文章和文档迁移到gitbook
  • 2015
    • 博客文章和文档迁移到gitbook
    • 给客户端写得LRU缓存
    • 近期活动比较零散
    • 关于BUS通信系统的一些思考(三)
    • 针对Java JIT的优化(转表工具:xresloader)
    • libcopp更新 (merge boost 1.59 context)
    • 小记最近踩得两个C++坑
    • Redis全异步(HA)Driver设计稿
    • Vim常用命令
    • 关于firewalld和systemd的一些命令速记
    • Jenkins(hudson)插件记录
    • 我们的Lua类绑定机制
    • LLVM+Clang+Libcxx+Libcxxabi(3.6)工具链编译(完成自举编译)
    • 回顾2014
    • Android NDK undefined reference to ___tls_get_addr 错误
    • gitlab腾讯企业邮箱配置
  • 2014
    • 回顾2013
    • C++11动态模板参数和type_traits
    • C++又一坑:动态链接库中的全局变量
    • tolua++内存释放坑
    • [转]类似github的框架
    • Lua性能分析
    • 集成Qt Webkit 到cocos2d-x
    • Gitlab环境搭建小计
    • 近期研究VPN的一些记录(OpenVPN,pptp,l2tp)
    • LLVM + Clang + Libcxx + Libcxxabi 工具链编译
    • 关于BUS通信系统的一些思考(二)
    • 关于BUS通信系统的一些思考(一)
    • [libiniloader] Project
    • 记录一些在线编辑器
    • [WP Code Highlight.js] Project
    • 再议 C++ 11 Lambda表达式
    • 基于Chrome插件的开发工具链
    • [ACM] HDU 1006 解题报告
    • Linux 编译安装 GCC 4.9
    • 又碰到了这个解谜游戏,顺带记下地址
    • 简单C++单元测试框架(支持一键切到GTest或Boost.Test)
    • 捣鼓一个协程库
  • 2013
    • std和boost的function与bind实现剖析
    • 不知道是哪一年的腾讯马拉松题目 照片评级 解题报告
    • Lua 挺好用的样子
    • VC和GCC成员函数指针实现的研究(三)
    • VC和GCC成员函数指针实现的研究(二)
    • VC和GCC内成员函数指针实现的研究(一)
    • 一个C++关于成员变量偏移地址的小Trick
    • ptmalloc,tcmalloc和jemalloc内存分配策略研究
    • POJ 2192 Zipper HDU 2059 龟兔赛跑
    • 从Javascript到Typescript到Node.js
    • 网络编程小结
    • 试试Boost.Asio
    • Lnmp yum 安装脚本 (for CentOS)
    • ARM 交叉编译环境搭建
    • Linux 编译安装 GCC 4.8
    • [记录]虚拟硬盘的压缩|磁盘写零
  • 2012
    • Boost.Spirit 初体验
    • “C++的90个坑”-阅读笔记
    • AC自动机
    • C++ 标准过渡期
    • 程序员修炼之道 -- 阅读笔记
    • [转载]狼与哈士奇
    • C++ 新特性学习(八) — 原子操作和多线程库[多工内存模型]
    • C++ 新特性学习(七) — 右值引用
    • 理解Protobuf的数据编码规则
    • 忆往昔ECUST的ACM时代
    • Linux编译安装GCC 4.7
    • JSON显示库 -- showJson (Javascript)
    • C++ 新特性学习(六) — 新的字符串编码和伪随机数
    • C++ 新特性学习(五) — 引用包装、元编程的类型属性和计算函数对象返回类型
    • C++ 新特性学习(四) — Bind和Function
  • 2011
    • C++ 新特性学习(三) — Regex库
    • C++ 新特性学习(二) -- Array、Tuple和Hash库
    • C++ 新特性学习(一) -- 概述+智能指针(smart_ptr)
    • Linux 和 Windows PowerShell 常用工具/命令 记录
    • 非常帅气的Linq to sql
    • 2011 Google Code Jam 小记
    • C++总是很神奇
    • 大学生创新项目[国家级]经费使用记录
    • 常用官方文档整理
    • 我们学校的IPV6很不错嘛
  • 2010
    • 线段树相关问题 (引用 PKU POJ题目) 整理
    • 2010 ACM 赛前笔记
    • POJ PKU 2596 Dice Stacking 解题报告
    • POJ PKU 3631 Cuckoo Hashing 解题报告
    • POJ PKU 1065 Wooden Sticks 3636 Nested Dolls 解题报告
    • HDU 3336 Count the string 解题报告
    • Hash模板 个人模板
    • ZOJ 3309 Search New Posts 解题报告
    • POJ PKU Let's Go to the Movies 解题报告
    • 注册表常用键值意义
    • PKU POJ 1724 ROADS 解题报告
    • 《神奇古今秘方集锦》&《民间秘术大全》
    • PKU POJ 1720 SQUARES 解题报告
    • POJ PKU 2155 Matrix 解题报告
    • PKU POJ 1141 Brackets Sequence 解题报告
    • PKU POJ 2728 Desert King 解题报告
    • PKU POJ 2976 Dropping tests 解题报告
    • PKU POJ 3757 Simple Distributed storage system 解题报告
    • GCD Determinant 解题报告
    • Southeastern European 2008 Sky Code 解题报告
    • HDU HDOJ 3400 Line belt 解题报告
    • 线性筛法求质数(素数)表 及其原理
    • HDU HDOJ 3398 String 解题报告
    • 树状数组模块(个人模板)
    • 浙江理工 省赛总结 team62 By OWenT of Coeus
    • POJ PKU 3659 Cell Phone Network 解题报告
    • USACO 2008 March Gold Cow Jogging 解题报告
    • C#格式化输出(记录)
    • 参加有道难题笔记
    • POJ PKU 2446 Chessboard 解题报告
    • POJ PKU 1986 Distance Queries 解题报告
    • 计算几何算法概览[转载]
    • 关于差分约束(转载)
    • POJ PKU 2826 An Easy Problem?! 解题报告
    • 数论模板(个人模板)
    • 简易四则运算(ACM个人模板)
    • Catalan 数
    • The 35th ACM/ICPC Asia Regional Tianjin Site —— Online Contest 1009 Convex 解题报告
    • JQuery扩展插件--提示信息
    • ACM 计算几何 个人模板
    • 解析网站字符串型参数 Javascript QueryString 操作 TQueryString类
    • POJ PKU 1474 Video Surveillance 解题报告
  • 2009
    • 模式匹配(kmp)个人模板
    • 并查集 模板
    • POJ 3267 The Cow Lexicon 解题报告
    • C/C++语言常用排序算法
    • POJ 2606 Rabbit hunt 2780 Linearity 1118 Lining Up 解题报告
    • 打造最快的Hash表(转) [以暴雪的游戏的Hash为例]
    • ECUST 09年 校赛个人赛第六,七场总结
    • ECUST 09年 校赛个人赛第三场部分解题报告(A,D,F,I)
    • 牛顿迭代解方程 ax^3+bX^2+cx+d=0
    • 09年8月9日 ECUST ACM 练习赛总结
    • 连接最多点直线 (OWenT 个人模板)
    • 点到直线距离 和 线段间最短距离 (OWenT 模板)
    • ECUST 09年 校赛个人训练赛第五场总结
    • ECUST 09年 校赛个人赛第八场(最后一场)总结
    • 09年8月14日 ECUST ACM 练习赛总结
    • 矩阵相关 (增强中)
    • Prime最小生成树(个人模板)
    • 最长单调子序列 复杂度nlog(n)
    • POJ PKU 2549 Sumsets 解题报告
    • POJ PKU 3277 City Horizon 解题报告
    • 我的ACM生涯
    • POJ PKU 2528 Mayor's posters 解题报告
    • POJ PKU 2378 Tree Cutting 解题报告
    • POJ PKU 1990 MooFest 解题报告
Powered by GitBook
On this page
  • BUG分析
  • BUG验证
  • Fix it
  • 后记

Was this helpful?

  1. 2016

pbc的一个陈年老BUG

PreviousAndroid和IOS的TLS问题Nextboost.context-1.61版本的设计模型变化

Last updated 5 years ago

Was this helpful?

近期跟了一下pbc的lua-binding的一个老BUG,起源是我们客户端报了一个奇怪的问题,我们游戏里的某些功能的optional字段,服务器并没有下发数据,但是客户端竟然能读到。

一开始我去issues里翻,翻到了个这个, 。 这是2014年就发现的BUG了,然而云风说考虑到性能问题不想改。大事我觉得吧,就算一些性能损失,也好过容易出BUG,并且协议打解包在游戏中的CPU占用本身就不重。所以这能自己动手,丰衣足食喽。

BUG分析

这里就得提到pbc的两个feature。第一个是,如果一个optional字段的数据不存在,那么你尝试去读的时候,会返回一个默认值。这个默认值的话如果协议里不定义,整数是0,字符串是空串,而table就会生成一个新table,里面的数据全是默认值。 第二个是,如果某个optional的字段的内容是默认值,那么打包的时候会忽略。也就是说pack的结果的二进制里什么也没有。

说它是两个feature是因为,原生的protobuf是没有这回事儿的。二进制里没有,unpack的结果里去读也不会有;有数据但是是默认值,打包的二进制里也会有key-value,只是value是默认值(0或者空串),默认值一般都很小。

然后既然要自己动手,就得去读代码了。对以上features的实现,特别是读取不存在的数据的实现,并且为了高效pbc采用了默认值table的方法。什么叫默认值table呢?就是每一个protubuf的message,在第一次解包的时候,pbc的lua-binding都会生成一个默认值的table。 假设某个要读取的数据不存在,pbc的lua-binding会返回这个默认值的table。举个例子:

local msg1 = pbc.unpack('abc', buffer1)

-- 假设这时候nodata这个数据不存在
print(msg1.nodata.int_type)
-- 假设0是默认值,这里会输出0

local msg2 = pbc.unpack('abc', buffer2)
-- 如果buffer1和buffer2里都没有nodata的数据,下面两行会输出同一个table
print(msg1.nodata)
print(msg2.nodata)

那么问题就来了,这种情况,如果对解包数据是只读还好,一旦进行了写入,假设执行了msg1.nodata.int_type = 100,那么msg2.nodata.int_type也变成了100,并且以后解包的这个message的空数据里的nodata.int_type都变成了100。这也是最开始提到的那个BUG的原因。

以上在lua-binding 5.1里的代码如下:

local function default_table(typename)
    local v = default_cache[typename]
    if v then
        return v
    end
    v = { __index = assert(decode_message(typename , "")) }
    default_cache[typename]  = v
    return v
end

BUG验证

**message Profile** {
  optional string nick_name = 1;
  optional string icon = 2;
}

message Person {
  required string name = 1;
  required int32 id = 2;        // Unique ID number for this person.
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
  repeated int32 test = 5 [packed=true];

  **optional Profile profile = 6;**

  extensions 10 to max;
}
local addressbook1 = {
    name = "Alice",
    id = 12345,
    phone = {
        { number = "1301234567" },
        { number = "87654321", type = "WORK" },
        { number = "13912345678", type = "MOBILE" },
    },
    email = "username@domain.com"
}

local addressbook2 = {
    name = "Bob",
    id = 12346,
    phone = {
        { number = "1301234568" },
        { number = "98765432", type = "HOME" },
        { number = "13998765432", type = "MOBILE" },
    }
}


code1 = protobuf.encode("tutorial.Person", addressbook1)
code2 = protobuf.encode("tutorial.Person", addressbook2)

decode1 = protobuf.decode("tutorial.Person" , code1)

-- BUG [ISSUE#27](https://github.com/cloudwu/pbc/issues/27)
decode1.profile.nick_name = "AHA"
decode1.profile.icon = "id:1"

decode2 = protobuf.decode("tutorial.Person" , code2)

function print_addr(decoded)
    print(string.format('ID: %d, Name: %s, Email: %s', decoded.id, decoded.name, tostring(decoded.email)))
    if decoded.profile then
        print(string.format('\tNickname: %s, Icon: %s', tostring(decoded.profile.nick_name), tostring(decoded.profile.icon)))
    end
    for k, v in ipairs(decoded.phone) do
        print(string.format("\tPhone NO.%s: %16s %s", k, v.number, tostring(v.type)))
    end
end

print_addr(decode1)
print_addr(decode2)

输出的内容如下

$ lua5.1 testparser.lua
ID: 12345, Name: Alice, Email: username@domain.com
        Nickname: AHA, Icon: id:1
        Phone NO.1:       1301234567 HOME
        Phone NO.2:         87654321 WORK
        Phone NO.3:      13912345678 MOBILE
ID: 12346, Name: Bob, Email:
        Nickname: AHA, Icon: id:1
        Phone NO.1:       1301234568 HOME
        Phone NO.2:         98765432 HOME
        Phone NO.3:      13998765432 MOBILE

第二个地址簿的Profile字段不应该有内容

Fix it

这个功能大致是新建一个table,然后把metatable的__index指向原来的table。那么访问的时候如果没有改变过数据,会访问原来的table,然后如果有修改,也不会修改原table里的数据。代码改动如下:

local function default_table(typename)
    local v = default_cache[typename]
    if v then
        return v
    end
    local default_inst = assert(decode_message(typename , ""))
    v = {
        __index = function(tb, key)
            local ret = default_inst[key]
            if 'table' ~= type(ret) then
                return ret
            end
            ret = setmetatable({}, { __index = ret })
            rawset(tb, key, ret)
            return ret
        end
    }
    default_cache[typename]  = v
    return v
end

这里新建了两个table,其中一个作为metatable。之所以不走“只建立一个table并把index设为自己”这种方式。是因为在我们的代码中很多情况是解包的数据改一改然后直接又扔给pbc作打包了。那么这时候就不想新增这个莫名其妙的index字段。

改完以后上面的test输出如下:

$ lua5.1 testparser.lua
ID: 12345, Name: Alice, Email: username@domain.com
        Nickname: AHA, Icon: id:1
        Phone NO.1:       1301234567 HOME
        Phone NO.2:         87654321 WORK
        Phone NO.3:      13912345678 MOBILE
ID: 12346, Name: Bob, Email:
        Nickname: , Icon:
        Phone NO.1:       1301234568 HOME
        Phone NO.2:         98765432 HOME
        Phone NO.3:      13998765432 MOBILE
Alice   123
Phone:      18612345678 HOME

后记

最新进展: 此PR已被云风官方仓库合入

知道问题原因了,验证这个BUG就很容易了。并且这个BUG并不仅仅是 里提到的repeated字段的问题,所有的默认table都有。 很简单地改动一下,给test/addressbook.proto加一个optional的message

我标记了新增的协议部分,完整内容见: 。然后 binding/lua/testparser.lua 里多加一个对比数据

完整内容见:

接下来就是修这个BUG了,前面已经说了是改掉了默认table。首先因为要向前兼容,之前提到的两个feature不能破坏掉,其次还要尽可能减少只读情况下的消耗。那么有一个简单的实现方式,那就是和我之前写得一个小函数类似。 里的class.protect

这样的话,仅仅在尝试解包不存在的数据时会有一次RTTI的开销和两个table的开销。而如果数据存在的话,开销和之前没有变化,我觉得还是可以接受的。 另外 里提到的BUG也就没有了。

这个fix已经推送给云风 ,然而他说不想做得更复杂和建议做深拷贝,而不是这种方式,所以没有merge。 但是做深拷贝确实开销会比较大,感觉得不偿失。同时 这个BUG很容带来使用者的误解而且容易遗漏。因为一个不正确的数据往往不会立即引发问题,而是绕了好几轮以后才发现。所以目前我们的client的pbc还是使用这个打了patch的分支。

分支地址是:

https://github.com/cloudwu/pbc/issues/27
https://github.com/cloudwu/pbc/issues/27
https://github.com/owent-contrib/pbc/blob/master/test/addressbook.proto
https://github.com/owent-contrib/pbc/blob/master/binding/lua/testparser.lua
https://github.com/owent-utils/lua/blob/master/src/utils/class.lua
https://github.com/cloudwu/pbc/issues/27
https://github.com/cloudwu/pbc/pull/80
https://github.com/owent-contrib/pbc/