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


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


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


27 天前 issue状态由 进行中 改变为 已解决
27 天前 关闭了 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 / 选填)