Rework client quitting and handling of remote errors

Client quitting no longer immediately unwinds, client is just pushed
for deletion until we get back to the main loop, similarly to what
happens for buffer and window deletion.
This commit is contained in:
Maxime Coste 2016-09-04 17:54:07 +01:00
parent 563497ade7
commit 4fc20b8d7d
10 changed files with 107 additions and 100 deletions

View File

@ -72,8 +72,6 @@ void Client::handle_available_input(EventMode mode)
return;
}
try
{
try
{
while (Optional<Key> key = get_next_key(mode))
@ -98,11 +96,6 @@ void Client::handle_available_input(EventMode mode)
context().print_status({ error.what().str(), get_face("Error") });
context().hooks().run_hook("RuntimeError", error.what(), context());
}
}
catch (Kakoune::client_removed& removed)
{
ClientManager::instance().remove_client(*this, removed.graceful);
}
}
void Client::print_status(DisplayLine status_line)

View File

@ -23,6 +23,7 @@ void ClientManager::clear()
// So that clients destructor find the client manager empty
// so that local UI does not fork.
ClientList clients = std::move(m_clients);
m_client_trash.clear();
}
String ClientManager::generate_name() const
@ -45,8 +46,6 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui,
std::move(ws.selections), std::move(env_vars),
generate_name()};
m_clients.emplace_back(client);
try
{
try
{
CommandManager::instance().execute(init_commands, client->context());
@ -57,14 +56,9 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui,
client->context().hooks().run_hook("RuntimeError", error.what(),
client->context());
}
}
catch (Kakoune::client_removed& removed)
{
remove_client(*client, removed.graceful);
return nullptr;
}
return client;
// Do not return the client if it already got moved to the trash
return contains(m_clients, client) ? client : nullptr;
}
void ClientManager::handle_pending_inputs() const
@ -75,10 +69,13 @@ void ClientManager::handle_pending_inputs() const
void ClientManager::remove_client(Client& client, bool graceful)
{
auto it = find_if(m_clients,
[&](const std::unique_ptr<Client>& ptr)
{ return ptr.get() == &client; });
kak_assert(it != m_clients.end());
auto it = find(m_clients, &client);
if (it == m_clients.end())
{
kak_assert(contains(m_client_trash, &client));
return;
}
m_client_trash.push_back(std::move(*it));
m_clients.erase(it);
if (not graceful and m_clients.empty())
@ -155,6 +152,11 @@ void ClientManager::clear_window_trash()
m_window_trash.clear();
}
void ClientManager::clear_client_trash()
{
m_client_trash.clear();
}
bool ClientManager::validate_client_name(StringView name) const
{
return const_cast<ClientManager*>(this)->get_client_ifp(name) == nullptr;

View File

@ -7,13 +7,6 @@
namespace Kakoune
{
struct client_removed
{
client_removed(bool graceful) : graceful{graceful} {}
const bool graceful;
};
struct WindowAndSelections
{
std::unique_ptr<Window> window;
@ -58,10 +51,12 @@ public:
ByteCount cursor_pos = -1) const;
void clear_window_trash();
void clear_client_trash();
private:
String generate_name() const;
ClientList m_clients;
ClientList m_client_trash;
Vector<WindowAndSelections, MemoryDomain::Client> m_free_windows;
Vector<std::unique_ptr<Window>> m_window_trash;
};

View File

@ -344,12 +344,12 @@ const CommandDesc force_kill_cmd = {
};
template<bool force>
void quit()
void quit(Context& context)
{
if (not force and ClientManager::instance().count() == 1)
ensure_all_buffers_are_saved();
// unwind back to this client event handler.
throw client_removed{ true };
ClientManager::instance().remove_client(context.client(), true);
}
const CommandDesc quit_cmd = {
@ -361,7 +361,7 @@ const CommandDesc quit_cmd = {
CommandFlags::None,
CommandHelper{},
CommandCompleter{},
[](const ParametersParser&, Context&, const ShellContext&){ quit<false>(); }
[](const ParametersParser&, Context& context, const ShellContext&){ quit<false>(context); }
};
const CommandDesc force_quit_cmd = {
@ -374,7 +374,7 @@ const CommandDesc force_quit_cmd = {
CommandFlags::None,
CommandHelper{},
CommandCompleter{},
[](const ParametersParser&, Context&, const ShellContext&){ quit<true>(); }
[](const ParametersParser&, Context& context, const ShellContext&){ quit<true>(context); }
};
template<bool force>
@ -382,7 +382,7 @@ void write_quit(const ParametersParser& parser, Context& context,
const ShellContext& shell_context)
{
write_buffer(parser, context, shell_context);
quit<force>();
quit<force>(context);
}
const CommandDesc write_quit_cmd = {
@ -419,7 +419,7 @@ const CommandDesc writeall_quit_cmd = {
[](const ParametersParser& parser, Context& context, const ShellContext&)
{
write_all_buffers();
quit<false>();
quit<false>(context);
}
};

View File

@ -451,7 +451,7 @@ int run_client(StringView session, StringView init_command, UIType ui_type)
while (true)
event_manager.handle_next_events(EventMode::Normal);
}
catch (peer_disconnected&)
catch (remote_error&)
{
write_stderr("disconnected from server\n");
return -1;
@ -524,11 +524,6 @@ int run_server(StringView session, StringView init_command,
startup_error = true;
write_to_debug_buffer(format("error while parsing kakrc:\n {}", error.what()));
}
catch (Kakoune::client_removed&)
{
startup_error = true;
write_to_debug_buffer("error while parsing kakrc: asked to quit");
}
{
Context empty_context{Context::EmptyContextFlag{}};
@ -589,6 +584,7 @@ int run_server(StringView session, StringView init_command,
client_manager.redraw_clients();
event_manager.handle_next_events(EventMode::Normal);
client_manager.handle_pending_inputs();
client_manager.clear_client_trash();
client_manager.clear_window_trash();
buffer_manager.clear_buffer_trash();
string_registry.purge_unused();

View File

@ -80,6 +80,8 @@ public:
template<typename U>
T value_or(U&& fallback) const { return m_valid ? m_value : T{fallback}; }
void reset() { destruct_ifn(); m_valid = false; }
private:
void destruct_ifn() { if (m_valid) m_value.~T(); }

View File

@ -54,7 +54,7 @@ public:
*reinterpret_cast<uint32_t*>(m_stream.data()+1) = (uint32_t)m_stream.size();
int res = ::write(m_socket, m_stream.data(), m_stream.size());
if (res == 0)
throw peer_disconnected{};
throw remote_error{"peer disconnected"};
}
void write(const char* val, size_t size)
@ -171,8 +171,8 @@ public:
void read(char* buffer, size_t size)
{
if (m_stream.size() - m_read_pos < size)
throw peer_disconnected{};
if (m_read_pos + size > m_stream.size())
throw remote_error{"tried to read after message end"};
memcpy(buffer, m_stream.data() + m_read_pos, size);
m_read_pos += size;
}
@ -229,7 +229,7 @@ private:
{
int res = ::read(sock, m_stream.data() + m_write_pos, size);
if (res == 0)
throw peer_disconnected{};
throw remote_error{"peer disconnected"};
if (res < 0)
throw socket_error{};
m_write_pos += res;
@ -325,21 +325,49 @@ public:
void set_ui_options(const Options& options) override;
void set_client(Client* client) { m_client = client; }
Client* client() const { return m_client.get(); }
private:
FDWatcher m_socket_watcher;
MsgReader m_reader;
CharCoord m_dimensions;
InputCallback m_input_callback;
SafePtr<Client> m_client;
Optional<Key> m_pending_key;
};
RemoteUI::RemoteUI(int socket, CharCoord dimensions)
: m_socket_watcher(socket, [this](FDWatcher& watcher, EventMode mode) {
const int sock = watcher.fd();
bool disconnect = false;
try
{
while (fd_readable(sock) and not m_reader.ready())
m_reader.read_available(sock);
if (m_reader.ready() and m_input_callback)
if (m_reader.ready() and not m_pending_key)
{
if (m_reader.type() == MessageType::Key)
{
m_pending_key = m_reader.read<Key>();
m_reader.reset();
}
else
disconnect = true;
}
}
catch (remote_error& err)
{
write_to_debug_buffer(format("Error while reading remote message: {}", err.what()));
disconnect = true;
}
if (disconnect)
ClientManager::instance().remove_client(*m_client, false);
else if (m_pending_key and m_input_callback)
m_input_callback(mode);
}),
m_dimensions(dimensions)
@ -427,32 +455,17 @@ void RemoteUI::set_ui_options(const Options& options)
bool RemoteUI::is_key_available()
{
return m_reader.ready();
return (bool)m_pending_key;
}
Key RemoteUI::get_key()
{
kak_assert(m_reader.ready());
try
{
if (m_reader.type() != MessageType::Key)
throw client_removed{ false };
Key key = m_reader.read<Key>();
m_reader.reset();
kak_assert(m_pending_key);
auto key = *m_pending_key;
m_pending_key.reset();
if (key.modifiers == Key::Modifiers::Resize)
m_dimensions = key.coord();
return key;
}
catch (peer_disconnected&)
{
throw client_removed{ false };
}
catch (socket_error&)
{
write_to_debug_buffer("ungraceful deconnection detected");
throw client_removed{ false };
}
}
CharCoord RemoteUI::dimensions()
@ -632,10 +645,12 @@ private:
auto init_command = m_reader.read<String>();
auto dimensions = m_reader.read<CharCoord>();
auto env_vars = m_reader.read_idmap<String, MemoryDomain::EnvVars>();
std::unique_ptr<UserInterface> ui{new RemoteUI{sock, dimensions}};
ClientManager::instance().create_client(std::move(ui),
std::move(env_vars),
init_command);
RemoteUI* ui = new RemoteUI{sock, dimensions};
if (auto* client = ClientManager::instance().create_client(
std::unique_ptr<UserInterface>{ui},
std::move(env_vars), init_command))
ui->set_client(client);
Server::instance().remove_accepter(this);
return;
}
@ -652,7 +667,6 @@ private:
write_to_debug_buffer(format("error running command '{}': {}",
command, e.what()));
}
catch (client_removed&) {}
close(sock);
Server::instance().remove_accepter(this);
return;

View File

@ -12,7 +12,12 @@
namespace Kakoune
{
struct peer_disconnected {};
struct remote_error : runtime_error
{
remote_error(String error)
: runtime_error{std::move(error)}
{}
};
struct connection_failed : runtime_error
{

View File

@ -1 +1 @@
<c-l>:q<ret>

View File

@ -1 +1 @@
w<c-l>:q<ret>
w