已关闭
[Bug-Report|缺陷反馈]: 并发时多次分配 DVPP RR 写值设备内存泄露 #808
WuXuefeng创建于 8月11日关闭于 6 天前
8月11日 将 xiao_cun 设为负责人
ykl999
8月11日 评论:
8月11日 评论:
感谢指出问题,我们会尽快分析并且fix相关问题


8月11日 将 xiangyang0909 设为负责人
xiangyang0909
8月11日 评论:
8月11日 评论:
感谢提供这么完整的复现和日志。我们按你给出的调用链核对了当前 master 以及相关维护分支,初步分析,确认这个问题成立。
GetDvppRRTaskAddr() 目前只对分配过程加了锁。首次并发调用时,多个线程可能都先在锁外看到地址为空,之后再依次进入临界区重复分配。后一次分配会覆
盖前一次地址,而 Stream 销毁时只能释放最后保存的地址,因此会造成设备内存泄漏。这个问题我们会按缺陷处理,修复进展后续在 issue 里同步。


24 天前 将 xwxf 设为负责人
24 天前 移除了负责人 xiangyang0909
24 天前 移除了负责人 xiao_cun
ykl999
6 天前 评论:
6 天前 评论:
问题已提pr自修复,issue关闭处理


6 天前 issue状态由 进行中 改变为 已解决
6 天前 关闭了 issue
Thanks for sending an issue! Please fill in the following template to help quickly solve your problem.
Describe the current behavior / 问题描述 (Mandatory / 必填)
Stream::GetDvppRRTaskAddr()(src/runtime/core/src/stream/stream.cc:4358)存在双重检查锁缺陷:锁内缺少二次空检查,并发时多次分配 DVPP RR 写值设备内存。
void* Stream::GetDvppRRTaskAddr(void) { if (dvppRRTaskAddr_ != nullptr) { // 锁外无同步快速路径 return dvppRRTaskAddr_; } const std::lock_guard<std::mutex> lock(dvppRRTaskAddrLock_); const rtError_t error = device_->Driver_()->DevMemAlloc(&dvppRRTaskAddr_, ...); // 锁内未 二次检查 ... }触发路径:
rtLaunchDvppTask → LaunchDvppTask → StarsLaunch(sqeHeader.reserved==1 且 DVPP 类 型)→ StarsLaunchDvppRRProcess → GetDvppRRTaskAddr()。多个线程并发向同一 DVPP 流提交任务时,都会通过锁外空检查,各自执行一次 DevMemAlloc 并覆盖 dvppRRTaskAddr_,造成设备内存泄漏(只保留
最后一次分配)和对 dvppRRTaskAddr_ 的数据竞争。
真机复现
aclrt)加载含缺陷代码的库。
修复建议
在锁内获取锁后再次检查 dvppRRTaskAddr_,仅在仍为空时才分配:
void* Stream::GetDvppRRTaskAddr(void) { if (dvppRRTaskAddr_ != nullptr) { return dvppRRTaskAddr_; } const std::lock_guard<std::mutex> lock(dvppRRTaskAddrLock_); // 二次检查:避免并发线程重复分配并覆盖,导致设备内存泄漏 if (dvppRRTaskAddr_ != nullptr) { return dvppRRTaskAddr_; } const rtError_t error = device_->Driver_()->DevMemAlloc(&dvppRRTaskAddr_, DVPP_RR_WRITE_VALUE_LEN, RT_MEMORY_DEFAULT, device_->Id_()); ... }Environment / 环境信息 (Mandatory / 必填)
问题触发前提是:该硬件平台支持 DVPP 任务提交(RT_FEATURE_TASK_DVPP),并通过
rtLaunchDvppTask/rtMultipleTaskInfoLaunch 提交 reserved=1 的 DVPP(VPC/JPEG)任务,从而进入
StarsLaunchDvppRRProcess → Stream::GetDvppRRTaskAddr()。
910_B_93
GetDvppRRTaskAddr() 的“锁内缺二次检查”缺陷在以下分支均存在(逐分支核实源码):
DVPP RR 写值缓冲区并发重复分配问题从 CANN Runtime 8.5.0 起贯穿到当前 master,凡启用 DVPP任务特性(上表 Ascend 平台)且并发向同一 DVPP 流提交任务即可触发。
Steps to reproduce the issue / 重现步骤 (Mandatory / 必填)
cmdlLis 内存,构造 VPC sqe(type=12, reserved=1),8 个线程并发 rtLaunchDvppTask。
线程(tid 9921/9923/9924)同时进入分配路径,DevMemAlloc 实际执行 3 次;多轮运行统计为2~3 次(竞争窗口所致),而正确行为应恒为 1 次。
dvpp_test.cpp:
#include <atomic> #include <cstdio> #include <cstring> #include <thread> #include <vector> #include "acl/acl_rt.h" #include "runtime/runtime/rt.h" #include "runtime/runtime/rt_stars.h" #include "runtime/rt_external_stars.h" #include "runtime/rt_external_stars_define.h" static void CheckR(const char* name, rtError_t e){ printf("[%s] ret=0x%x\n", name, e); } int main() { aclError ae = aclInit(nullptr); printf("[aclInit] ret=%d\n", ae); ae = aclrtSetDevice(0); printf("[aclrtSetDevice] ret=%d\n", ae); if (ae != ACL_SUCCESS) return 1; rtContext_t cur = nullptr; rtError_t e = rtCtxGetCurrent(&cur); CheckR("rtCtxGetCurrent", e); printf("current ctx=%p\n", cur); rtDvppGrp_t grp = nullptr; e = rtDvppGroupCreate(&grp, 0); CheckR("rtDvppGroupCreate", e); rtStream_t stm = nullptr; e = rtStreamCreateByGrp(&stm, 0, 0, grp); CheckR("rtStreamCreateByGrp", e); void* cmdList = nullptr; e = rtMalloc(&cmdList, 4096, RT_MEMORY_DEFAULT, 0); CheckR("rtMalloc-cmdList", e); printf("cmdList=%p\n", cmdList); rtStarsCommonSqe_t sqe; std::memset(&sqe, 0, sizeof(sqe)); sqe.sqeHeader.type = 12; /* VPC */ sqe.sqeHeader.reserved = 1; /* trigger DVPP RR write-value tasks */ sqe.sqeHeader.wrCqe = 1; const uint64_t cmd = reinterpret_cast<uint64_t>(cmdList); sqe.commandCustom[12] = static_cast<uint32_t>(cmd & 0xFFFFFFFFULL); sqe.commandCustom[13] = static_cast<uint32_t>(cmd >> 32); constexpr int kThreads = 8; std::atomic<int> ok{0}; std::atomic<uint32_t> ready{0}; std::atomic<bool> go{false}; std::vector<std::thread> ts; ts.reserve(kThreads); for (int i = 0; i < kThreads; ++i) { ts.emplace_back([&]() { rtCtxSetCurrent(cur); /* bind current context to this thread */ ready.fetch_add(1, std::memory_order_release); while (!go.load(std::memory_order_acquire)) std::this_thread::yield(); const rtError_t r = rtLaunchDvppTask(&sqe, sizeof(sqe), stm, nullptr); if (r == RT_ERROR_NONE) ok.fetch_add(1); printf(" launch ret=0x%x\n", r); }); } while (ready.load(std::memory_order_acquire) < kThreads) std::this_thread::yield(); go.store(true, std::memory_order_release); for (auto& t : ts) t.join(); printf("launch OK count=%d/%d\n", ok.load(), kThreads); e = rtStreamSynchronize(stm); CheckR("rtStreamSynchronize", e); if (cmdList) rtFree(cmdList); if (grp) rtDvppGroupDestory(grp); if (stm) rtStreamDestroy(stm); aclrtResetDevice(0); aclFinalize(); printf("done\n"); return 0; }Describe the expected behavior / 预期结果 (Mandatory / 必填)
测试程序:dvpp_test.cpp——aclrtSetDevice 后创建 DVPP 组、基于组创建流、分配
cmdlLis 内存,构造 VPC sqe(type=12, reserved=1),8 个线程并发 rtLaunchDvppTask。
线程(tid 9921/9923/9924)同时进入分配路径,DevMemAlloc 实际执行 3 次;多轮运行统计为2~3 次(竞争窗口所致),而正确行为应恒为 1 次。
Related log / screenshot / 日志 / 截图 (Mandatory / 必填)
运行结果
关键日志(复现 bug 证据)
同一 stream_id=47 下,Stream::GetDvppRRTaskAddr()(stream.cc:4375)被多个线程同时进入分配路
径,执行了多次 DevMemAlloc:
[INFO] RUNTIME(13799,repro4):...[stream.cc:4375] 13807 GetDvppRRTaskAddr: stream_id=47
[INFO] RUNTIME(13799,repro4):...[stream.cc:4375] 13813 GetDvppRRTaskAddr: stream_id=47
运行统计:GetDvppRRTaskAddr 分配次数 = 2(线程 13807、13813 同时通过锁外空检查各自分
配);正确行为应为 1 次。多轮运行该值在 2~3 之间波动,符合竞争窗口特征。
Special notes for this issue/备注 (Optional / 选填)