ssh_terminal: avoid deadlock of EP and pthread.0

pthread.0 acquires a write buffer mutex and calls potentially
blocking fs operations. The EP thread handles session requests and tries to
acquire the same write buffer lock. IO progress events for pthread.0 are
handled by the EP thread, which however is blocking on the write buffer mutex.

The commit uses two write buffers, one which is filled by the EP and a second
which is used by pthread.0. The two buffers are swapped protected by a mutex
without invoking blocking fs operations.

Issue #4095
This commit is contained in:
Alexander Boettcher 2021-04-28 08:49:23 +02:00 committed by Norman Feske
parent 86e09b60c4
commit f236e99b5c
2 changed files with 50 additions and 29 deletions

View File

@ -86,16 +86,16 @@ class Terminal::Session_component : public Genode::Rpc_object<Session, Session_c
Genode::size_t _write(Genode::size_t num)
{
ssize_t written = 0;
Libc::with_libc([&] () {
char *buf = _io_buffer.local_addr<char>();
num = Genode::min(num, _io_buffer.size());
written = Ssh::Terminal::write(buf, num);
if (written < 0) {
Genode::error("write error, dropping data");
written = 0;
}
});
char *buf = _io_buffer.local_addr<char>();
num = Genode::min(num, _io_buffer.size());
written = Ssh::Terminal::write(buf, num);
if (written < 0) {
Genode::error("write error, dropping data");
written = 0;
}
return written;
}
};

View File

@ -37,15 +37,16 @@ namespace Ssh
class Ssh::Terminal
{
public:
Util::Buffer<4096u> read_buf { };
int write_avail_fd { -1 };
private:
Util::Buffer<4096u> _write_buf { };
typedef Util::Buffer<4096u> Buffer;
Mutex _write_buf_swap { };
Buffer _write_buf_a { };
Buffer _write_buf_b { };
Buffer *_write_buf_ep { &_write_buf_a };
Buffer *_write_buf_pthread { &_write_buf_b };
::Terminal::Session::Size _size { 0, 0 };
@ -60,6 +61,10 @@ class Ssh::Terminal
public:
Buffer read_buf { };
int write_avail_fd { -1 };
/**
* Constructor
*/
@ -159,15 +164,27 @@ class Ssh::Terminal
*/
void send(ssh_channel channel)
{
Mutex::Guard guard(_write_buf.mutex());
{
/* swap write buffers if current is empty */
Mutex::Guard guard(_write_buf_swap);
if (!_write_buf.read_avail()) { return; }
if (!_write_buf_pthread->read_avail()) {
auto buffer_tmp = _write_buf_ep;
_write_buf_ep = _write_buf_pthread;
_write_buf_pthread = buffer_tmp;
}
}
/* write buffer for pthread is used w/o mutex, it's not used by EP */
Buffer &write_buf = *_write_buf_pthread;
if (!write_buf.read_avail()) { return; }
/* ignore send request */
if (!channel || !ssh_channel_is_open(channel)) { return; }
char const *src = _write_buf.content();
size_t const len = _write_buf.read_avail();
char const *src = write_buf.content();
size_t const len = write_buf.read_avail();
/* XXX we do not handle partial writes */
int const num_bytes = ssh_channel_write(channel, src, len);
@ -176,7 +193,7 @@ class Ssh::Terminal
}
if (++_pending_channels >= _attached_channels) {
_write_buf.reset();
write_buf.reset();
}
/* at this point the client might have disconnected */
@ -214,24 +231,28 @@ class Ssh::Terminal
*/
size_t write(char const *src, Genode::size_t src_len)
{
Mutex::Guard g(_write_buf.mutex());
size_t num_bytes = 0;
size_t i = 0;
Libc::with_libc([&] {
while (_write_buf.write_avail() > 0 && i < src_len) {
{
Mutex::Guard guard(_write_buf_swap);
Buffer &write_buf = *_write_buf_ep;
size_t i = 0;
while (write_buf.write_avail() > 0 && i < src_len) {
char c = src[i];
if (c == '\n') {
_write_buf.append('\r');
write_buf.append('\r');
}
_write_buf.append(c);
write_buf.append(c);
num_bytes++;
i++;
}
});
}
/* wake the event loop up */
Libc::with_libc([&] {