Skip to content

Commit 8f3af08

Browse files
committed
Add a unit test for receiving 'finish' message after channel close
After the input channel is closed, the messages that were previously written to the fd should still be readable. However, in some cases, if the fd is closed with data in the fd's buffer, the data may be discarded. This is confirmed by the attached unit test. Add an explanation of this to the InputChannel implementation, so that this behaviour is documented. Bug: 376713684 Test: TEST=libinput_tests; m $TEST && adb sync data && adb shell -t /data/nativetest64/$TEST/$TEST --gtest_filter="*ReceiveAfterClose*" --gtest_repeat=100000 --gtest_break_on_failure Test: TEST=libinput_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_filter="*ReceiveAfterClose*" --gtest_repeat=100000 --gtest_break_on_failure Flag: TEST_ONLY Change-Id: I67a6a6432c4756283c8f2cca3c01210a3bcdb42e
1 parent 85add72 commit 8f3af08

4 files changed

Lines changed: 173 additions & 10 deletions

File tree

include/input/InputTransport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,4 +454,6 @@ class InputPublisher {
454454
InputVerifier mInputVerifier;
455455
};
456456

457+
std::ostream& operator<<(std::ostream& out, const InputMessage& msg);
458+
457459
} // namespace android

libs/input/InputConsumerNoResampling.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,6 @@ InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTim
171171
return msg;
172172
}
173173

174-
std::ostream& operator<<(std::ostream& out, const InputMessage& msg) {
175-
out << ftl::enum_string(msg.header.type);
176-
return out;
177-
}
178-
179174
} // namespace
180175

181176
// --- InputConsumerNoResampling ---

libs/input/InputTransport.cpp

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,16 +436,29 @@ android::base::Result<InputMessage> InputChannel::receiveMessage() {
436436
if (error == EAGAIN || error == EWOULDBLOCK) {
437437
return android::base::Error(WOULD_BLOCK);
438438
}
439-
if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED) {
440-
return android::base::Error(DEAD_OBJECT);
439+
if (error == EPIPE) {
440+
return android::base::ResultError("Got EPIPE", DEAD_OBJECT);
441+
}
442+
if (error == ENOTCONN) {
443+
return android::base::ResultError("Got ENOTCONN", DEAD_OBJECT);
444+
}
445+
if (error == ECONNREFUSED) {
446+
return android::base::ResultError("Got ECONNREFUSED", DEAD_OBJECT);
447+
}
448+
if (error == ECONNRESET) {
449+
// This means that the client has closed the channel while there was
450+
// still some data in the buffer. In most cases, subsequent reads
451+
// would result in more data. However, that is not guaranteed, so we
452+
// should not return WOULD_BLOCK here to try again.
453+
return android::base::ResultError("Got ECONNRESET", DEAD_OBJECT);
441454
}
442455
return android::base::Error(-error);
443456
}
444457

445458
if (nRead == 0) { // check for EOF
446-
ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
447-
"channel '%s' ~ receive message failed because peer was closed", name.c_str());
448-
return android::base::Error(DEAD_OBJECT);
459+
LOG_IF(INFO, DEBUG_CHANNEL_MESSAGES)
460+
<< "channel '" << name << "' ~ receive message failed because peer was closed";
461+
return android::base::ResultError("::recv returned 0", DEAD_OBJECT);
449462
}
450463

451464
if (!msg.isValid(nRead)) {
@@ -766,4 +779,9 @@ android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveC
766779
return android::base::Error(UNKNOWN_ERROR);
767780
}
768781

782+
std::ostream& operator<<(std::ostream& out, const InputMessage& msg) {
783+
out << ftl::enum_string(msg.header.type);
784+
return out;
785+
}
786+
769787
} // namespace android

libs/input/tests/InputChannel_test.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <time.h>
2121
#include <errno.h>
2222

23+
#include <android-base/logging.h>
2324
#include <binder/Binder.h>
2425
#include <binder/Parcel.h>
2526
#include <gtest/gtest.h>
@@ -43,6 +44,39 @@ bool operator==(const InputChannel& left, const InputChannel& right) {
4344
return left.getName() == right.getName() &&
4445
left.getConnectionToken() == right.getConnectionToken() && lhs.st_ino == rhs.st_ino;
4546
}
47+
48+
/**
49+
* Read a message from the provided channel. Read will continue until there's data, so only call
50+
* this if there's data in the channel, or it's closed. If there's no data, this will loop forever.
51+
*/
52+
android::base::Result<InputMessage> readMessage(InputChannel& channel) {
53+
while (true) {
54+
// Keep reading until we get something other than 'WOULD_BLOCK'
55+
android::base::Result<InputMessage> result = channel.receiveMessage();
56+
if (!result.ok() && result.error().code() == WOULD_BLOCK) {
57+
// The data is not available yet.
58+
continue; // try again
59+
}
60+
return result;
61+
}
62+
}
63+
64+
InputMessage createFinishedMessage(uint32_t seq) {
65+
InputMessage finish{};
66+
finish.header.type = InputMessage::Type::FINISHED;
67+
finish.header.seq = seq;
68+
finish.body.finished.handled = true;
69+
return finish;
70+
}
71+
72+
InputMessage createKeyMessage(uint32_t seq) {
73+
InputMessage key{};
74+
key.header.type = InputMessage::Type::KEY;
75+
key.header.seq = seq;
76+
key.body.key.action = AKEY_EVENT_ACTION_DOWN;
77+
return key;
78+
}
79+
4680
} // namespace
4781

4882
class InputChannelTest : public testing::Test {
@@ -227,6 +261,120 @@ TEST_F(InputChannelTest, SendAndReceive_MotionClassification) {
227261
}
228262
}
229263

264+
/**
265+
* In this test, server writes 3 key events to the client. The client, upon receiving the first key,
266+
* sends a "finished" signal back to server, and then closes the fd.
267+
*
268+
* Next, we check what the server receives.
269+
*
270+
* In most cases, the server will receive the finish event, and then an 'fd closed' event.
271+
*
272+
* However, sometimes, the 'finish' event will not be delivered to the server. This is communicated
273+
* to the server via 'ECONNRESET', which the InputChannel converts into DEAD_OBJECT.
274+
*
275+
* The server needs to be aware of this behaviour and correctly clean up any state associated with
276+
* the client, even if the client did not end up finishing some of the messages.
277+
*
278+
* This test is written to expose a behaviour on the linux side - occasionally, the
279+
* last events written to the fd by the consumer are not delivered to the server.
280+
*
281+
* When tested on 2025 hardware, ECONNRESET was received approximately 1 out of 40 tries.
282+
* In vast majority (~ 29999 / 30000) of cases, after receiving ECONNRESET, the server could still
283+
* read the client data after receiving ECONNRESET.
284+
*/
285+
TEST_F(InputChannelTest, ReceiveAfterCloseMultiThreaded) {
286+
std::unique_ptr<InputChannel> serverChannel, clientChannel;
287+
status_t result =
288+
InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel);
289+
ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
290+
291+
// Sender / publisher: publish 3 keys
292+
InputMessage key1 = createKeyMessage(/*seq=*/1);
293+
serverChannel->sendMessage(&key1);
294+
// The client should close the fd after it reads this one, but we will send 2 more here.
295+
InputMessage key2 = createKeyMessage(/*seq=*/2);
296+
serverChannel->sendMessage(&key2);
297+
InputMessage key3 = createKeyMessage(/*seq=*/3);
298+
serverChannel->sendMessage(&key3);
299+
300+
std::thread consumer = std::thread([clientChannel = std::move(clientChannel)]() mutable {
301+
// Read the first key
302+
android::base::Result<InputMessage> firstKey = readMessage(*clientChannel);
303+
if (!firstKey.ok()) {
304+
FAIL() << "Did not receive the first key";
305+
}
306+
307+
// Send finish
308+
const InputMessage finish = createFinishedMessage(firstKey->header.seq);
309+
clientChannel->sendMessage(&finish);
310+
// Now close the fd
311+
clientChannel.reset();
312+
});
313+
314+
// Now try to read the finish message, even though client closed the fd
315+
android::base::Result<InputMessage> response = readMessage(*serverChannel);
316+
consumer.join();
317+
if (response.ok()) {
318+
ASSERT_EQ(response->header.type, InputMessage::Type::FINISHED);
319+
} else {
320+
// It's possible that after the client closes the fd, server will receive ECONNRESET.
321+
// In those situations, this error code will be translated into DEAD_OBJECT by the
322+
// InputChannel.
323+
ASSERT_EQ(response.error().code(), DEAD_OBJECT);
324+
// In most cases, subsequent attempts to read the client channel at this
325+
// point would succeed. However, for simplicity, we exit here (since
326+
// it's not guaranteed).
327+
return;
328+
}
329+
330+
// There should not be any more events from the client, since the client closed fd after the
331+
// first key.
332+
android::base::Result<InputMessage> noEvent = serverChannel->receiveMessage();
333+
ASSERT_FALSE(noEvent.ok()) << "Got event " << *noEvent;
334+
}
335+
336+
/**
337+
* Similar test as above, but single-threaded.
338+
*/
339+
TEST_F(InputChannelTest, ReceiveAfterCloseSingleThreaded) {
340+
std::unique_ptr<InputChannel> serverChannel, clientChannel;
341+
status_t result =
342+
InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel);
343+
ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
344+
345+
// Sender / publisher: publish 3 keys
346+
InputMessage key1 = createKeyMessage(/*seq=*/1);
347+
serverChannel->sendMessage(&key1);
348+
// The client should close the fd after it reads this one, but we will send 2 more here.
349+
InputMessage key2 = createKeyMessage(/*seq=*/2);
350+
serverChannel->sendMessage(&key2);
351+
InputMessage key3 = createKeyMessage(/*seq=*/3);
352+
serverChannel->sendMessage(&key3);
353+
354+
// Read the first key
355+
android::base::Result<InputMessage> firstKey = readMessage(*clientChannel);
356+
if (!firstKey.ok()) {
357+
FAIL() << "Did not receive the first key";
358+
}
359+
360+
// Send finish
361+
const InputMessage finish = createFinishedMessage(firstKey->header.seq);
362+
clientChannel->sendMessage(&finish);
363+
// Now close the fd
364+
clientChannel.reset();
365+
366+
// Now try to read the finish message, even though client closed the fd
367+
android::base::Result<InputMessage> response = readMessage(*serverChannel);
368+
ASSERT_FALSE(response.ok());
369+
ASSERT_EQ(response.error().code(), DEAD_OBJECT);
370+
371+
// We can still read the finish event (but in practice, the expectation is that the server will
372+
// not be doing this after getting DEAD_OBJECT).
373+
android::base::Result<InputMessage> finishEvent = serverChannel->receiveMessage();
374+
ASSERT_TRUE(finishEvent.ok());
375+
ASSERT_EQ(finishEvent->header.type, InputMessage::Type::FINISHED);
376+
}
377+
230378
TEST_F(InputChannelTest, DuplicateChannelAndAssertEqual) {
231379
std::unique_ptr<InputChannel> serverChannel, clientChannel;
232380

0 commit comments

Comments
 (0)