SurfaceFlinger稳定性01-Layer泄漏问题日志增强方案
| 作者 | 时间 | 描述 |
|---|---|---|
| 贾勇强 | 2024.04.15 | system_server、sf范围添加dump信息和相关日志 |
文档地址 https://xiaomi.f.mioffice.cn/docx/doxk47tUJYR69gPB3qSzlDPe9hg
Layer泄漏潜在影响因素较多、涉及业务模块多,在项目交付过程中出现问题时,经常很难进一步定位具体原因。
因此,这里针对Layer泄漏场景添加些必要信息,帮忙研发在遇到问题时能快速定位问题。
导致Layer泄漏的主要原因有:
- SurfaceControl操作不合理。
◦ 业务侧在使用完毕SurfaceControl后,没有执行SurfaceControl.release()进行释放;
◦ 业务侧在释放时,如果存在父SurfaceControl,没有执行SurfaceControl.reparent(null);
◦ 业务侧创建了多份SurfaceControl副本,且没有对所有副本都执行以上操作。
- LayerHandle操作不合理。
◦ SurfaceControl未析构,持有LayerHandle引用;
◦ ComposerState未析构,持有LayerHandle引用。
- Layer操作不合理。
◦ 释放时没有将Parent Layer置空;
◦ 仅对Parent Layer进行释放,未释放子Layer;

一、确认业务侧是否释放SurfaceControl
surfaceflinger进程内的信息无法明确确认业务侧SurfaceControl是否释放,比如当LayerHandle未析构时,并不能直接说明业务侧SurfaceControl中还持有LayerHandle,sf进程内部有可能也存在对LayerHandle的间接引用(比如通过TransactionState持有)。
同时,SurfaceControl和Layer并非一对一的关系,如果一个SurfaceControl通过copy另一个SurfaceControl创建,其共享同一个Layer,并且只有他们全部释放后,才会析构LayerHandle(如果sf侧没有LayerHandle引用)。
1.1、解决方案:业务进程添加关键日志信息和dump信息
原生代码中已实现对SurfaceControl泄漏相关逻辑,这里直接对原生逻辑进行应用。
在SurfaceControl创建时,通过SurfaceControlRegistry类对每个进程会进行SC的统一,我们可以利用这个类进行日志和dump信息添加。
在创建SurfaceControl时,将其添加到SurfaceControlRegistry中,当该进程内SurfaceControl个数超过阈值时,输出相关日志,代码逻辑如下:
// frameworks/base/core/java/android/view/SurfaceControl.java
// 创建SurfaceControl时,会将其添加到列表中
private void addToRegistry() {
final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
if (registry != null) {
registry.add(this);
}
}
// frameworks/base/core/java/android/view/SurfaceControlRegistry.java
// 添加过程中,当超过设置阈值时,会输出日志
void add(SurfaceControl sc) {
synchronized (sLock) {
mSurfaceControls.put(sc, SystemClock.elapsedRealtime());
if (!mHasReportedExceedingMaxThreshold
&& mSurfaceControls.size() >= mMaxLayersReportingThreshold) {
PrintWriter pw = new PrintWriter(System.out, true /* autoFlush */);
mReporter.onMaxLayersExceeded(mSurfaceControls, DUMP_LIMIT, pw);
}
}
}
// dump时,输出当前存留未释放SurfaceControl
public static void dump(int limit, boolean runGc, PrintWriter pw) {
......
synchronized (sLock) {
if (sProcessRegistry != null) {
sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw);
}
}
}
1.1.1、system_server接入
system_server中接入SurfaceControlRegistry分两部分:
-
WMS启动过程中初始化SurfaceControlRegistry实例;
-
执行gfxinfo、window dumpsys输出信息中添加当前SurfaceControl输出;
1.1.1.1、初始化SurfaceControlRegistry
在WindowMangerService启动时,可以通过SurfaceControlRegistry对system_server进程内的SurfaceControl进行统计和输出。这里添加相关代码如下:
WMS启动过程中,创建system_server进程内SurfaceControlRegistry实例:
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private WindowManagerService(Context context, InputManagerService inputManager,
boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,
DisplayWindowSettingsProvider displayWindowSettingsProvider,
Supplier<SurfaceControl.Transaction> transactionFactory,
Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
........
// MIUI ADD: SF_LogEnhance
// Used for debugging layer leaks.
// 创建SurfaceControlRegistry实例
SurfaceControlRegistry.createProcessInstance(mContext);
// END SF_LogEnhance
}
1.1.1.2、dumpsys输出添加SurfaceControl信息
对当前SurfaceControl信息输出,在dump gfxinfo和window中分别进行了添加,以支持其他业务进程的信息输出。
• wms中,携带参数执行dump时可输出system_server侧SurfaceControl信息:
adb shell dumpsys window surfacecontrol
gfxinfo中,可以为所有进程提供SurfaceControl输出

1.1.2、SystemUI/Lancher/壁纸接入
初步针对当前Layer泄漏/DMA-Buf泄漏高发场景systemui、Lancher、壁纸,接入该方案。
业务侧接入方式:进程启动后注册一个SurfaceControlRegistry实例即可,如:
import android.view.SurfaceControlRegistry;
public Method() {
// 创建进程内单例SurfaceControlRegistry对象
SurfaceControlRegistry.createProcessInstance(mApplicationContext);
}1.2、log信息输出
adb logcat | grep -Ei "I System.out:|SurfaceControlRegistry"
1.3、dump 信息输出
dump命令:
adb shell dumpsys window surfacecontrol // 仅输出system_server内SC
# 或
adb shell dumpsys gfxinfo system
业务侧以SystemUI为例,dump及输出结果如下:
adb shell dumpsys gfxinfo com.android.systemui
https://gerrit.pt.mioffice.cn/c/platform/frameworks/base/+/3940078
📌
当问题发生时,如果system_server或相关进程侧无泄漏信息,但sf侧中泄漏Layer显示LayerHandle未释放,即可排除SurfaceControl.release()方法未调用的可能性。
二、SurfaceControl已经释放,但LayerHandle未释放
如果所有引用LayerHandle的SurfaceControl已确认释放,但LayerHandle并未释放,基本可以确定LayerHandle在surfaceflinger进程内部存在引用。
系统规定LayerHandle只能作为暴露给客户端用于操作Layer的”句柄”,不能在surfaceflinger内部出现任何引用它的地方,不过却存在间接使用它的地方——TransactionState。
所有客户端的Transaction发起提交后,会以TranactionState的方式传递给surfaceflinger中,TranactionState中会携带有LayerHandle。
surfaceflinger中收到来自客户端传递的Transaction后,会把这些TranactionState入对,并且在下一帧VSNC对所有队列中的Tranaction进行处理,并在应用完成后清空队列。

问题在于,如果TranactionState队列一直没有清空,那么即便SurfaceControl释放,LayerHandle也无法进行析构,这时将导致Layer泄漏。
TranactionState队列堆积原因一般有以下几种:
-
Buffer Barrier未移除;
-
Buffer desiredPresentTime未到达;
-
Buffer Fence信号未收到。
后两种情况很少见,重点是第一种情况,一旦给Barrier Buffer设置的frameNumber出现异常,将直接导致相同ApplyToken的Transaction队列中的所有Transaction堆积。
2.1 解决方案:dump Transaction队列信息、添加针对Barrier Buffer相关日志
https://gerrit.pt.mioffice.cn/c/platform/frameworks/native/+/3935615
在TransactionHandler中,添加dump信息:
// frameworks/native/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
// MIUI ADD: SF_LogEnhance
std::string TransactionHandler::dump() {
std::string result;
const nsecs_t now = systemTime();
base::StringAppendF(&result, "\nPending transaction queue state(size=%d):\n",
mPendingTransactionQueues.size());
base::StringAppendF(&result, " Total transactions: %d\n",
static_cast<int>(mPendingTransactionCount.load()));
auto it = mPendingTransactionQueues.begin();
while (it != mPendingTransactionQueues.end()) {
auto& [token, queue] = *it;
base::StringAppendF(&result, " ApplyToken: %p, size: %d", token.get(), queue.size());
// We only dump the first one transaction from queues corresponding to each token,
// it's enough for debugging barrier buffer.
if (!queue.empty()) {
auto& transactionState = queue.front();
base::StringAppendF(&result,
dumpTransactionState(transactionState, now).c_str());
}
++it;
}
return result;
}
std::string TransactionHandler::dumpTransactionState(const TransactionState& ts,
const nsecs_t& now) {
std::string result;
int maxLayerCount = 10;
bool startDump = false;
result.append("\n ");
base::StringAppendF(&result,
"TransactionState: id=%" PRIu64 ", originPid=%d"
", desiredPresentTime=%" PRId64 "(%.2fms ago), postTime=%" PRId64
"(%.2fms ago)"
", deferAnimation=%d, flags: %" PRIu32,
ts.id, ts.originPid, ts.desiredPresentTime,
(now - ts.desiredPresentTime) / 1e6f, ts.postTime,
(now - ts.postTime) / 1e6f, ts.deferAnimation, ts.flags);
result.append("\n ");
base::StringAppendF(&result, "Resolved states(size=%d): ", ts.states.size());
for (auto& resolvedState : ts.states) {
sp<Layer> layer = LayerHandle::getLayer(resolvedState.state.surface);
std::string layerName =
(layer) ? layer->getDebugName() : std::to_string(resolvedState.layerId);
bool hasVaildBuffer = resolvedState.state.hasValidBuffer();
bool hasBarrier = hasVaildBuffer && resolvedState.state.bufferData->hasBarrier;
// Only dump layer with buffer barrier and its last 10 layers.
startDump |= hasBarrier;
if (startDump) {
result.append("\n ");
base::StringAppendF(&result, " Layer: %s, layerId: %" PRIu32 ", parentId: %" PRIu32,
layerName.c_str(), resolvedState.layerId, resolvedState.parentId);
if (hasBarrier) {
result.append("\n ");
base::StringAppendF(&result, "hasBuffer: %s, hasBufferBarrier: %s",
hasVaildBuffer ? "true" : "false",
hasBarrier ? "true" : "false");
result.append("\n ");
base::StringAppendF(&result,
"drawing barrierFrameNumber: %" PRIu64
", pending barrierFrameNumber: %" PRIu64,
layer->getDrawingState().barrierFrameNumber,
resolvedState.state.bufferData->barrierFrameNumber);
}
--maxLayerCount;
}
if (maxLayerCount < 0) {
break;
}
}
result.append("\n");
return result;
}为避免输出信息过多,对输出内容进行精简:
-
只输出TransactionState队列中的队头TransactionState,它是阻塞之源;
-
针对每个TransactionState中的Layer,从携带BufferBarrier的ComposerState开始向后最多输出10个ComposerState信息。
2.1.1、dump信息输出
出现问题时,输出结果如下
adb shell dumpsys SurfaceFlinger
1: 以ApplyToken为Key键、TransactionState列表为值的映射表元素个数;
2: 所有TransactionState的个数;
3: TransactionState列表中的元素个数;
4: ResolvedState列表中对应操作Layer的个数;
5: GraphicBuffer设置了barrier的具体Layer。
三、OffScreenLayer泄漏
OffScreenLayer泄漏,一般有以下几类场景:
-
LayerHandle已释放,但Layer没有析构时,会加入OffScreenLayer列表中无法移除;
-
LayerHandle未释放,但其Parent Layer释放后,该Layer会加入OffScreenLayer列表无法移除;
-
LayerHandle未释放,对其进行reparent时,将其reparent到一个空Parent或OffScreenLayer上时,该Layer会加入OffScreenLayer列表无法移除;
3.1、解决方案: OffscreenLayer dump信息中添加parent信息

// frameworks/native/services/surfaceflinger/Layer.cpp
void Layer::dumpOffscreenDebugInfo(std::string& result) const {
std::string hasBuffer = hasBufferOrSidebandStream() ? " (contains buffer)" : "";
// MIUI MOD: SF_LogEnhance
// StringAppendF(&result, "Layer %s%s pid:%d uid:%d%s\n", getName().c_str(), hasBuffer.c_str(),
// mOwnerPid, mOwnerUid, isHandleAlive() ? " handleAlive" : "");
StringAppendF(&result, "Layer %s%s pid:%d uid:%d%s%s\n", getName().c_str(), hasBuffer.c_str(),
mOwnerPid, mOwnerUid, isHandleAlive() ? " handleAlive" : "",
getParent() ? std::string(" parent(")
.append(getParent()->getDebugName())
.append(")")
.c_str()
: "");
// END SF_LogEnhance
}3.2、dump信息输出
执行adb shell dumpsys SurfaceFlinger后,输出结果如下:

仅有handleAlive:说明LayerHandle没有释放且没有Parent,可确认是reparent(null)导致;
- handleAlive + parent都存在: 说明LayerHandle没有释放且存在Parent,可确认是reparent(OffscreenLayer)导致;
- 仅有parent:说明LayerHandle已经释放,但其Parent没有释放;
四、代码提交
surfaceflinger侧改动:
https://gerrit.pt.mioffice.cn/c/platform/frameworks/native/+/3935615
system_server侧改动:
https://gerrit.pt.mioffice.cn/c/platform/frameworks/base/+/3940078