Kill misbehaving subscribed clients instead of hanging

This change only affects clients that are subscribed to events, which
should be the main cause of our problems.

In the common case (no buffered data) the behaviour doesn't change at
all: the message is sent directly, no ev_io / ev_timeout callback is
enabled. Once a write to a client's socket is not completed fully
(returns with EAGAIN error), we put the message in the tail of a queue
and init an ev_io callback and a corresponding timer. If the timer is
triggered first, the socket is closed and the client connection is
removed. If the socket becomes writeable before the timeout we either
reset the timer if we couldn't push all the buffered data or completely
remove it if everything was pushed.

We could also replace ipc_send_message() for all client connections in
i3, not just those subscribed to events.

Furthermore, we could limit the amount of messages stored and increase
the timeout (or use multiple timeouts): eg it's ok if a client is not
reading for 10 seconds and we are only holding 5KB of messages for them
but it is not ok if they are inactive for 5 seconds and we have 30MB of
messages held.

Closes #2999
Closes #2539
This commit is contained in:
Orestis Floros
2018-04-23 12:20:05 +03:00
parent b0bbe53d04
commit 37d0105c83
10 changed files with 287 additions and 5 deletions

View File

@ -501,6 +501,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
workspace
ipc_socket
ipc-socket
ipc_kill_timeout
restart_state
popup_during_fullscreen
exec_always

View File

@ -0,0 +1,69 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Test that i3 will not hang if a connected client stops reading from its
# subscription socket and that the client is killed after a delay.
# Ticket: #2999
# Bug still in: 4.15-180-g715cea61
use i3test i3_config => <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Set the timeout to 500ms to reduce the duration of this test.
ipc_kill_timeout 500
EOT
# Manually connect to i3 so that we can choose to not read events
use IO::Socket::UNIX;
my $sock = IO::Socket::UNIX->new(Peer => get_socket_path());
my $magic = "i3-ipc";
my $payload = '["workspace"]';
my $message = $magic . pack("LL", length($payload), 2) . $payload;
print $sock $message;
# Constantly switch between 2 workspaces to generate events.
fresh_workspace;
open_window;
fresh_workspace;
open_window;
eval {
local $SIG{ALRM} = sub { die "Timeout\n" };
# 500 is an arbitrarily large number to make sure that the socket becomes
# non-writeable.
for (my $i = 0; $i < 500; $i++) {
alarm 1;
cmd 'workspace back_and_forth';
alarm 0;
}
};
ok(!$@, 'i3 didn\'t hang');
# Wait for connection timeout
sleep 1;
use IO::Select;
my $s = IO::Select->new($sock);
my $reached_eof = 0;
while ($s->can_read(0.05)) {
if (read($sock, my $buffer, 100) == 0) {
$reached_eof = 1;
last;
}
}
ok($reached_eof, 'socket connection closed');
close $sock;
done_testing;