平台
R K 3568 + A n d r o i d 11 RK3568 + Android 11 RK3568+Android11
概述
官方的说明
如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框,ANR 对话框会为用户提供强制退出应用的选项。
ANR 处理流程
- 首先, 3个ANR的时间定义:
通过调试: adb shell dumpsys input中查看窗口信息的dispatchingTimeout, 不同的窗口可能是不一样的, 这得看实际的时间设定, 如下3个值都设置为5秒, 那显示的都为5秒.
App如Launhcer:
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
//当前的超时设置为5's
public static final int KEY_DISPATCHING_TIMEOUT_MS = 5 * 1000;
SystemUI的导航栏和状态栏:
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
///
// 这个时间关联到的是: //
///
// Default input dispatching timeout in nanoseconds.
static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L;
h.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
// Default input dispatching timeout if there is no focused application or paused window
// from which to determine an appropriate dispatching timeout.
constexpr std::chrono::nanoseconds DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5s;
- 第二, 字符串定义(frameworks/base/core/res 目录下):
<string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g>没有响应"</string>
<string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g>没有响应"</string>
<string name="anr_application_process" msgid="4978772139461676184">"<xliff:g id="APPLICATION">%1$s</xliff:g>没有响应"</string>
<string name="anr_process" msgid="1664277165911816067">"进程“<xliff:g id="PROCESS">%1$s</xliff:g>”没有响应"</string>
<string name="force_close" msgid="9035203496368973803">"确定"</string>
<string name="report" msgid="2149194372340349521">"报告"</string>
<string name="wait" msgid="7765985809494033348">"等待"</string>
时序
关键代码
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::doNotifyAnrLockedInterruptible(CommandEntry* commandEntry) {
sp<IBinder> token =
commandEntry->inputChannel ? commandEntry->inputChannel->getConnectionToken() : nullptr;
mLock.unlock();
const nsecs_t timeoutExtension =
mPolicy->notifyAnr(commandEntry->inputApplicationHandle, token, commandEntry->reason);
mLock.lock();
if (timeoutExtension > 0) {
extendAnrTimeoutsLocked(commandEntry->inputApplicationHandle, token, timeoutExtension);
} else {
// stop waking up for events in this connection, it is already not responding
sp<Connection> connection = getConnectionLocked(token);
if (connection == nullptr) {
return;
}
cancelEventsForAnrLocked(connection);
}
}
void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
// Since we are allowing the policy to extend the timeout, maybe the waitQueue
// is already healthy again. Don't raise ANR in this situation
if (connection->waitQueue.empty()) {
ALOGI("Not raising ANR because the connection %s has recovered",
connection->inputChannel->getName().c_str());
return;
}
/**
* The "oldestEntry" is the entry that was first sent to the application. That entry, however,
* may not be the one that caused the timeout to occur. One possibility is that window timeout
* has changed. This could cause newer entries to time out before the already dispatched
* entries. In that situation, the newest entries caused ANR. But in all likelihood, the app
* processes the events linearly. So providing information about the oldest entry seems to be
* most useful.
*/
DispatchEntry* oldestEntry = *connection->waitQueue.begin();
const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
std::string reason =
android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
connection->inputChannel->getName().c_str(),
ns2ms(currentWait),
oldestEntry->eventEntry->getDescription().c_str());
updateLastAnrStateLocked(getWindowHandleLocked(connection->inputChannel->getConnectionToken()),
reason);
std::unique_ptr<CommandEntry> commandEntry =
std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
commandEntry->inputApplicationHandle = nullptr;
commandEntry->inputChannel = connection->inputChannel;
commandEntry->reason = std::move(reason);
postCommandLocked(std::move(commandEntry));
}
/**
* Check if any of the connections' wait queues have events that are too old.
* If we waited for events to be ack'ed for more than the window timeout, raise an ANR.
* Return the time at which we should wake up next.
*/
nsecs_t InputDispatcher::processAnrsLocked() {
const nsecs_t currentTime = now();
nsecs_t nextAnrCheck = LONG_LONG_MAX;
// Check if we are waiting for a focused window to appear. Raise ANR if waited too long
if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
if (currentTime >= *mNoFocusedWindowTimeoutTime) {
onAnrLocked(mAwaitedFocusedApplication);
mAwaitedFocusedApplication.clear();
return LONG_LONG_MIN;
} else {
// Keep waiting
const nsecs_t millisRemaining = ns2ms(*mNoFocusedWindowTimeoutTime - currentTime);
ALOGW("Still no focused window. Will drop the event in %" PRId64 "ms", millisRemaining);
nextAnrCheck = *mNoFocusedWindowTimeoutTime;
}
}
// Check if any connection ANRs are due
nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
if (currentTime < nextAnrCheck) { // most likely scenario
return nextAnrCheck; // everything is normal. Let's check again at nextAnrCheck
}
// If we reached here, we have an unresponsive connection.
sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
if (connection == nullptr) {
ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
return nextAnrCheck;
}
connection->responsive = false;
// Stop waking up for this unresponsive connection
mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
//ANR触发位置
onAnrLocked(connection);
return LONG_LONG_MIN;
}
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
// If we are still waiting for ack on some events,
// we might have to wake up earlier to check if an app is anr'ing.
const nsecs_t nextAnrCheck = processAnrsLocked();
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
// We are about to enter an infinitely long sleep, because we have no commands or
// pending or queued events
if (nextWakeupTime == LONG_LONG_MAX) {
mDispatcherEnteredIdle.notify_all();
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
nsecs_t NativeInputManager::notifyAnr(const sp<InputApplicationHandle>& inputApplicationHandle,
const sp<IBinder>& token, const std::string& reason) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyANR");
#endif
ATRACE_CALL();
JNIEnv* env = jniEnv();
ScopedLocalFrame localFrame(env);
jobject inputApplicationHandleObj =
getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
jobject tokenObj = javaObjectForIBinder(env, token);
jstring reasonObj = env->NewStringUTF(reason.c_str());
jlong newTimeout = env->CallLongMethod(mServiceObj,
gServiceClassInfo.notifyANR, inputApplicationHandleObj, tokenObj,
reasonObj);
if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
newTimeout = 0; // abort dispatch
} else {
assert(newTimeout >= 0);
}
return newTimeout;
}
GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz,
"notifyANR",
"(Landroid/view/InputApplicationHandle;Landroid/os/IBinder;Ljava/lang/String;)J");
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
// Native callback.
private long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token,
String reason) {
return mWindowManagerCallbacks.notifyANR(inputApplicationHandle,
token, reason);
}
frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java
/**
* Notifies the window manager about an application that is not responding.
* Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
*
* Called by the InputManager.
*/
@Override
public long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token,
String reason) {
final long startTime = SystemClock.uptimeMillis();
try {
return notifyANRInner(inputApplicationHandle, token, reason);
} finally {
// Log the time because the method is called from InputDispatcher thread. It shouldn't
// take too long that may affect input response time.
Slog.d(TAG_WM, "notifyANR took " + (SystemClock.uptimeMillis() - startTime) + "ms");
}
}
private long notifyANRInner(InputApplicationHandle inputApplicationHandle, IBinder token,
String reason) {
ActivityRecord activity = null;
WindowState windowState = null;
boolean aboveSystem = false;
int windowPid = INVALID_PID;
preDumpIfLockTooSlow();
//TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
synchronized (mService.mGlobalLock) {
// Check if we can blame a window
if (token != null) {
windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
activity = windowState.mActivityRecord;
windowPid = windowState.mSession.mPid;
// Figure out whether this window is layered above system windows.
// We need to do this here to help the activity manager know how to
// layer its ANR dialog.
aboveSystem = isWindowAboveSystem(windowState);
}
}
// Check if we can blame an embedded window
if (token != null && windowState == null) {
EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(token);
if (embeddedWindow != null) {
windowPid = embeddedWindow.mOwnerPid;
WindowState hostWindowState = embeddedWindow.mHostWindowState;
if (hostWindowState == null) {
// The embedded window has no host window and we cannot easily determine
// its z order. Try to place the anr dialog as high as possible.
aboveSystem = true;
} else {
aboveSystem = isWindowAboveSystem(hostWindowState);
}
}
}
// Check if we can blame an activity. If we don't have an activity to blame, pull out
// the token passed in via input application handle. This can happen if there are no
// focused windows but input dispatcher knows the focused app.
if (activity == null && inputApplicationHandle != null) {
activity = ActivityRecord.forTokenLocked(inputApplicationHandle.token);
}
if (windowState != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to " + windowState.mAttrs.getTitle()
+ ". Reason: " + reason);
} else if (activity != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to application " + activity.stringName
+ ". Reason: " + reason);
} else {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ ". Reason: " + reason);
}
mService.saveANRStateLocked(activity, windowState, reason);
}
// All the calls below need to happen without the WM lock held since they call into AM.
mService.mAtmInternal.saveANRState(reason);
if (activity != null && activity.appToken != null) {
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
final boolean abort = activity.keyDispatchingTimedOut(reason, windowPid);
if (!abort) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
return activity.mInputDispatchingTimeoutNanos;
}
} else if (windowState != null || windowPid != INVALID_PID) {
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
long timeout = mService.mAmInternal.inputDispatchingTimedOut(windowPid, aboveSystem,
reason);
if (timeout >= 0) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
return timeout * 1000000L; // nanoseconds
}
}
return 0; // abort dispatching
}
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission " + FILTER_EVENTS);
}
ProcessRecord proc;
long timeout;
synchronized (this) {
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(pid);
}
timeout = proc != null ? proc.getInputDispatchingTimeout() : KEY_DISPATCHING_TIMEOUT_MS;
}
if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
return -1;
}
return timeout;
}
final class UiHandler extends Handler {
//Ignored....
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_ERROR_UI_MSG: {
mAppErrors.handleShowAppErrorUi(msg);
ensureBootCompleted();
} break;
case SHOW_NOT_RESPONDING_UI_MSG: {
mAppErrors.handleShowAnrUi(msg);
ensureBootCompleted();
} break;
//Ignored .....
}
frameworks/base/services/core/java/com/android/server/am/AnrHelper.java
void appNotResponding(ProcessRecord anrProcess, String annotation) {
appNotResponding(anrProcess, null /* activityShortComponentName */, null /* aInfo */,
null /* parentShortComponentName */, null /* parentProcess */,
false /* aboveSystem */, annotation);
}
void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
synchronized (mAnrRecords) {
mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
parentShortComponentName, parentProcess, aboveSystem, annotation));
}
startAnrConsumerIfNeeded();
}
private void startAnrConsumerIfNeeded() {
if (mRunning.compareAndSet(false, true)) {
new AnrConsumerThread().start();
}
}
frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
//Ignored...
// mUiHandler can be null if the AMS is constructed with injector only. This will only
// happen in tests.
if (mService.mUiHandler != null) {
// Bring up the infamous App Not Responding dialog
Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);
mService.mUiHandler.sendMessage(msg);
}
}
}
class ErrorDialogControlle
//AppErrors 调用
void showAnrDialogs(AppNotRespondingDialog.Data data) {
List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */);
mAnrDialogs = new ArrayList<>();
for (int i = contexts.size() - 1; i >= 0; i--) {
final Context c = contexts.get(i);
mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
}
mService.mUiHandler.post(() -> {
List<AppNotRespondingDialog> dialogs;
synchronized (mService) {
dialogs = mAnrDialogs;
}
if (dialogs != null) {
forAllDialogs(dialogs, Dialog::show);
}
});
}
frameworks/base/services/core/java/com/android/server/am/AppErrors.java
void handleShowAnrUi(Message msg) {
List<VersionedPackage> packageList = null;
synchronized (mService) {
AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
final ProcessRecord proc = data.proc;
if (proc == null) {
Slog.e(TAG, "handleShowAnrUi: proc is null");
return;
}
if (!proc.isPersistent()) {
packageList = proc.getPackageListWithVersionCode();
}
if (proc.getDialogController().hasAnrDialogs()) {
Slog.e(TAG, "App already has anr dialog: " + proc);
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.ALREADY_SHOWING);
return;
}
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
proc.getDialogController().showAnrDialogs(data);
//Ignored .....
}
frameworks/base/services/core/java/com/android/server/am/AppNotRespondingDialog.java
对话框的代码就省略吧.
至此, 显示ANR对话框的流程走完
InputDispatcher中窗口句柄
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::updateInputFlinger() {
ATRACE_CALL();
if (!mInputFlinger) {
return;
}
if (mVisibleRegionsDirty || mInputInfoChanged) {
mInputInfoChanged = false;
updateInputWindowInfo();
} else if (mInputWindowCommands.syncInputWindows) {
// If the caller requested to sync input windows, but there are no
// changes to input windows, notify immediately.
setInputWindowsFinished();
}
mInputWindowCommands.clear();
}
void SurfaceFlinger::updateInputWindowInfo() {
std::vector<InputWindowInfo> inputHandles;
mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
if (layer->needsInputInfo()) {
// When calculating the screen bounds we ignore the transparent region since it may
// result in an unwanted offset.
inputHandles.push_back(layer->fillInputInfo());
}
});
mInputFlinger->setInputWindows(inputHandles,
mInputWindowCommands.syncInputWindows ? mSetInputWindowsListener
: nullptr);
}
frameworks/native/services/inputflinger/InputManager.cpp
void InputManager::setInputWindows(const std::vector<InputWindowInfo>& infos,
const sp<ISetInputWindowsListener>& setInputWindowsListener) {
std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>> handlesPerDisplay;
std::vector<sp<InputWindowHandle>> handles;
for (const auto& info : infos) {
handlesPerDisplay.emplace(info.displayId, std::vector<sp<InputWindowHandle>>());
handlesPerDisplay[info.displayId].push_back(new BinderWindowHandle(info));
}
mDispatcher->setInputWindows(handlesPerDisplay);
if (setInputWindowsListener) {
setInputWindowsListener->onSetInputWindowsFinished();
}
}
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
///
// 通过InputManagerService 添加窗口句柄 //
///
/**
* Called from InputManagerService, update window handle list by displayId that can receive input.
* A window handle contains information about InputChannel, Touch Region, Types, Focused,...
* If set an empty list, remove all handles from the specific display.
* For focused handle, check if need to change and send a cancel event to previous one.
* For removed handle, check if need to send a cancel event if already in touch.
*/
void InputDispatcher::setInputWindowsLocked(){
updateWindowHandlesForDisplayLocked...
}
///
// 更新mWindowHandlesByDisplay中 //
///
void InputDispatcher::updateWindowHandlesForDisplayLocked(
const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
//Ignored.....
// Insert or replace
mWindowHandlesByDisplay[displayId] = newHandles;
}
///
// 从mWindowHandlesByDisplay中查询当前窗口 //
///
sp<InputWindowHandle> InputDispatcher::getWindowHandleLocked(
const sp<IBinder>& windowHandleToken) const {
if (windowHandleToken == nullptr) {
return nullptr;
}
for (auto& it : mWindowHandlesByDisplay) {
const std::vector<sp<InputWindowHandle>> windowHandles = it.second;
for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
if (windowHandle->getToken() == windowHandleToken) {
return windowHandle;
}
}
}
return nullptr;
}
nsecs_t InputDispatcher::getDispatchingTimeoutLocked(const sp<IBinder>& token) {
sp<InputWindowHandle> window = getWindowHandleLocked(token);
if (window != nullptr) {
return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT).count();
}
return DEFAULT_INPUT_DISPATCHING_TIMEOUT.count();
}
frameworks/base/services/core/java/com/android/server/wm/WindowProcessController.java
public long getInputDispatchingTimeout() {
synchronized (mAtm.mGlobalLock) {
return isInstrumenting() || isUsingWrapper()
? INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS : KEY_DISPATCHING_TIMEOUT_MS;
}
}
frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java
public long getInputDispatchingTimeout() {
return mWindowProcessController.getInputDispatchingTimeout();
}
扩展
编译及替换
#编译
mmm frameworks/native/services/inputflinger/ -j2
#替换
adb push out/target/product/rk3566_r/system/bin/inputflinger /system/bin/
adb push out/target/product/rk3566_r/system/lib/libinputflinger.so /system/lib/
adb push out/target/product/rk3566_r/system/lib64/libinputflinger.so /system/lib64/
调试: adb shell dumpsys input
0: name='Window{57ce99a u0 NavigationBar0}', displayId=0, portalToDisplayId=-1, paused=false, hasFocus=false, hasWallpaper=false, visible=false, canReceiveKeys=false, flags=0x21840068, type=0x000007e3, frame=[0,752][1280,800], globalScale=1.000000, windowScale=(1.000000,1.000000), touchableRegion=[0,752][1280,800], inputFeatures=0x00000000, ownerPid=614, ownerUid=10119, dispatchingTimeout=7000ms
1: name='Window{61c1033 u0 StatusBar}', displayId=0, portalToDisplayId=-1, paused=false, hasFocus=false, hasWallpaper=false, visible=false, canReceiveKeys=false, flags=0x81800408, type=0x000007d0, frame=[0,0][1280,24], globalScale=1.000000, windowScale=(1.000000,1.000000), touchableRegion=[0,0][1280,24], inputFeatures=0x00000000, ownerPid=614, ownerUid=10119, dispatchingTimeout=7000ms
2: name='Window{e0e47c4 u0 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher}', displayId=0, portalToDisplayId=-1, paused=false, hasFocus=true, hasWallpaper=true, visible=true, canReceiveKeys=true, flags=0x81910120, type=0x00000001, frame=[0,0][1280,800], globalScale=1.000000, windowScale=(1.000000,1.000000), touchableRegion=[0,0][1280,800], inputFeatures=0x00000000, ownerPid=1039, ownerUid=10121, dispatchingTimeout=15000ms
3: name='Window{bbadc9c u0 com.android.systemui.ImageWallpaper}', displayId=0, portalToDisplayId=-1, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x00014318, type=0x000007dd, frame=[-64,-304][1344,1104], globalScale=1.000000, windowScale=(0.909091,0.909091), touchableRegion=[-64,-304][1344,1104], inputFeatures=0x00000000, ownerPid=614, ownerUid=10119, dispatchingTimeout=7000ms
参考