From ae39986b14a7705b450253412ecf4e67be3b5dcd Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 9 Mar 2022 10:55:46 -0800 Subject: [PATCH 1/2] Add a message when a key response takes too long. --- .../Source/FlutterKeyboardManager.mm | 9 +++- .../Source/FlutterKeyboardManagerTest.mm | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm index db6aeca077bac..9f83ec23113fc 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm @@ -7,6 +7,7 @@ #include "flutter/fml/platform/darwin/message_loop_darwin.h" static constexpr CFTimeInterval kDistantFuture = 1.0e10; +static constexpr CFTimeInterval kReasonableInterval = 2.0; @interface FlutterKeyboardManager () @@ -114,7 +115,13 @@ - (void)handlePress:(nonnull FlutterUIPressProxy*)press // // We need to run in this mode so that UIKit doesn't give us new // events until we are done processing this one. - CFRunLoopRunInMode(fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kDistantFuture, NO); + CFRunLoopRunResult result = CFRunLoopRunInMode( + fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kReasonableInterval, NO); + if (result == kCFRunLoopRunTimedOut) { + NSLog(@"Flutter framework failed to process a key event in a reasonable time. Continuing " + @"to wait."); + CFRunLoopRunInMode(fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kDistantFuture, NO); + } break; } case UIPressPhaseChanged: diff --git a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm index de9fadabc34fb..a31e106c642af 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm @@ -7,6 +7,7 @@ #import #import #include <_types/_uint32_t.h> +#import #include "flutter/fml/platform/darwin/message_loop_darwin.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" @@ -375,4 +376,57 @@ - (void)testEventsProcessedSequentially API_AVAILABLE(ios(13.4)) { XCTAssertFalse(key1Handled); } +- (void)testLogWhenEventHandlingTakesTooLong API_AVAILABLE(ios(13.4)) { + constexpr UIKeyboardHIDUsage keyId1 = (UIKeyboardHIDUsage)0x50; + FlutterUIPressProxy* event1 = keyDownEvent(keyId1); + __block FlutterAsyncKeyCallback key1Callback; + __block bool key1Handled = true; + + // Redirect the stderr to a string buffer. + std::stringstream cerrBuffer; + std::streambuf* realCerr = std::cerr.rdbuf(cerrBuffer.rdbuf()); + + FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init]; + [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterUIPressProxy* press, + FlutterAsyncKeyCallback callback) { + if (press == event1) { + key1Callback = callback; + } + }]]; + + // Add both presses into the main CFRunLoop queue + CFRunLoopTimerRef timer0 = CFRunLoopTimerCreateWithHandler( + kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { + [manager handlePress:event1 + nextAction:^() { + key1Handled = false; + }]; + }); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer0, kCFRunLoopCommonModes); + + // Call the callback, but do it too late, so that we get a log message. + CFRunLoopTimerRef timer1 = CFRunLoopTimerCreateWithHandler( + kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 3, 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { + // No processing should be done on key2 yet + XCTAssertTrue(key1Callback != nil); + key1Callback(false); + }); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer1, + fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode); + + // Start a nested CFRunLoop so we can wait for both presses to complete before exiting the test + CFRunLoopRun(); + + // Check the log output. + std::cerr.flush(); + std::string logOutput = + cerrBuffer.str(); // Get the stderr output that happened while the loop was running. + XCTAssertTrue(logOutput.find("Flutter framework failed to process a key event in a reasonable " + "time. Continuing to wait.") != std::string::npos); + std::cerr.rdbuf(realCerr); + // In case there was other useful cerr output in the test, write it to the real cerr. + std::cerr << logOutput; + XCTAssertFalse(key1Handled); +} + @end From 4eb5a13ac26702dea45337145a6555ea424b9d48 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 9 Mar 2022 20:32:32 -0800 Subject: [PATCH 2/2] Add more log output, remove test --- .../Source/FlutterKeyboardManager.mm | 1 + .../Source/FlutterKeyboardManagerTest.mm | 54 ------------------- 2 files changed, 1 insertion(+), 54 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm index 9f83ec23113fc..d75e06536c2dd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm @@ -121,6 +121,7 @@ - (void)handlePress:(nonnull FlutterUIPressProxy*)press NSLog(@"Flutter framework failed to process a key event in a reasonable time. Continuing " @"to wait."); CFRunLoopRunInMode(fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kDistantFuture, NO); + NSLog(@"Flutter framework finally responded. Exiting nested event loop."); } break; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm index a31e106c642af..de9fadabc34fb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm @@ -7,7 +7,6 @@ #import #import #include <_types/_uint32_t.h> -#import #include "flutter/fml/platform/darwin/message_loop_darwin.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" @@ -376,57 +375,4 @@ - (void)testEventsProcessedSequentially API_AVAILABLE(ios(13.4)) { XCTAssertFalse(key1Handled); } -- (void)testLogWhenEventHandlingTakesTooLong API_AVAILABLE(ios(13.4)) { - constexpr UIKeyboardHIDUsage keyId1 = (UIKeyboardHIDUsage)0x50; - FlutterUIPressProxy* event1 = keyDownEvent(keyId1); - __block FlutterAsyncKeyCallback key1Callback; - __block bool key1Handled = true; - - // Redirect the stderr to a string buffer. - std::stringstream cerrBuffer; - std::streambuf* realCerr = std::cerr.rdbuf(cerrBuffer.rdbuf()); - - FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init]; - [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterUIPressProxy* press, - FlutterAsyncKeyCallback callback) { - if (press == event1) { - key1Callback = callback; - } - }]]; - - // Add both presses into the main CFRunLoop queue - CFRunLoopTimerRef timer0 = CFRunLoopTimerCreateWithHandler( - kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { - [manager handlePress:event1 - nextAction:^() { - key1Handled = false; - }]; - }); - CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer0, kCFRunLoopCommonModes); - - // Call the callback, but do it too late, so that we get a log message. - CFRunLoopTimerRef timer1 = CFRunLoopTimerCreateWithHandler( - kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 3, 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { - // No processing should be done on key2 yet - XCTAssertTrue(key1Callback != nil); - key1Callback(false); - }); - CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer1, - fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode); - - // Start a nested CFRunLoop so we can wait for both presses to complete before exiting the test - CFRunLoopRun(); - - // Check the log output. - std::cerr.flush(); - std::string logOutput = - cerrBuffer.str(); // Get the stderr output that happened while the loop was running. - XCTAssertTrue(logOutput.find("Flutter framework failed to process a key event in a reasonable " - "time. Continuing to wait.") != std::string::npos); - std::cerr.rdbuf(realCerr); - // In case there was other useful cerr output in the test, write it to the real cerr. - std::cerr << logOutput; - XCTAssertFalse(key1Handled); -} - @end