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:
parent
563497ade7
commit
4fc20b8d7d
|
@ -74,34 +74,27 @@ void Client::handle_available_input(EventMode mode)
|
|||
|
||||
try
|
||||
{
|
||||
try
|
||||
while (Optional<Key> key = get_next_key(mode))
|
||||
{
|
||||
while (Optional<Key> key = get_next_key(mode))
|
||||
if (*key == ctrl('c'))
|
||||
killpg(getpgrp(), SIGINT);
|
||||
else if (*key == Key::FocusIn)
|
||||
context().hooks().run_hook("FocusIn", context().name(), context());
|
||||
else if (*key == Key::FocusOut)
|
||||
context().hooks().run_hook("FocusOut", context().name(), context());
|
||||
else if (key->modifiers == Key::Modifiers::Resize)
|
||||
{
|
||||
if (*key == ctrl('c'))
|
||||
killpg(getpgrp(), SIGINT);
|
||||
else if (*key == Key::FocusIn)
|
||||
context().hooks().run_hook("FocusIn", context().name(), context());
|
||||
else if (*key == Key::FocusOut)
|
||||
context().hooks().run_hook("FocusOut", context().name(), context());
|
||||
else if (key->modifiers == Key::Modifiers::Resize)
|
||||
{
|
||||
m_window->set_dimensions(m_ui->dimensions());
|
||||
force_redraw();
|
||||
}
|
||||
else
|
||||
m_input_handler.handle_key(*key);
|
||||
m_window->set_dimensions(m_ui->dimensions());
|
||||
force_redraw();
|
||||
}
|
||||
}
|
||||
catch (Kakoune::runtime_error& error)
|
||||
{
|
||||
context().print_status({ error.what().str(), get_face("Error") });
|
||||
context().hooks().run_hook("RuntimeError", error.what(), context());
|
||||
else
|
||||
m_input_handler.handle_key(*key);
|
||||
}
|
||||
}
|
||||
catch (Kakoune::client_removed& removed)
|
||||
catch (Kakoune::runtime_error& error)
|
||||
{
|
||||
ClientManager::instance().remove_client(*this, removed.graceful);
|
||||
context().print_status({ error.what().str(), get_face("Error") });
|
||||
context().hooks().run_hook("RuntimeError", error.what(), context());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -47,24 +48,17 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui,
|
|||
m_clients.emplace_back(client);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
CommandManager::instance().execute(init_commands, client->context());
|
||||
}
|
||||
catch (Kakoune::runtime_error& error)
|
||||
{
|
||||
client->context().print_status({ error.what().str(), get_face("Error") });
|
||||
client->context().hooks().run_hook("RuntimeError", error.what(),
|
||||
client->context());
|
||||
}
|
||||
CommandManager::instance().execute(init_commands, client->context());
|
||||
}
|
||||
catch (Kakoune::client_removed& removed)
|
||||
catch (Kakoune::runtime_error& error)
|
||||
{
|
||||
remove_client(*client, removed.graceful);
|
||||
return nullptr;
|
||||
client->context().print_status({ error.what().str(), get_face("Error") });
|
||||
client->context().hooks().run_hook("RuntimeError", error.what(),
|
||||
client->context());
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(); }
|
||||
|
||||
|
|
|
@ -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,23 +325,51 @@ 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();
|
||||
while (fd_readable(sock) and not m_reader.ready())
|
||||
m_reader.read_available(sock);
|
||||
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)
|
||||
m_input_callback(mode);
|
||||
}),
|
||||
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)
|
||||
{
|
||||
write_to_debug_buffer(format("remote client connected: {}", m_socket_watcher.fd()));
|
||||
|
@ -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();
|
||||
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 };
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1 +1 @@
|
|||
<c-l>:q<ret>
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
w<c-l>:q<ret>
|
||||
w
|
||||
|
|
Loading…
Reference in New Issue
Block a user