mu: add --listen option for server
Add a --listen option for the server Rework the option code to share the --listen / socket-path code between scm and server subcommands. Move option off the stack in mu.cc, seems it's too big, at least when using gdb.
This commit is contained in:
@ -61,10 +61,16 @@ directly through the emacs process input/output. This is noticeably faster for
|
|||||||
commands with a lot of output, esp. when the the temp-file uses a in-memory
|
commands with a lot of output, esp. when the the temp-file uses a in-memory
|
||||||
file-system.
|
file-system.
|
||||||
|
|
||||||
|
** --listen
|
||||||
|
If set, the server starts an SCM REPL as well, which listens on a Unix domain
|
||||||
|
socket. This corresponds to the *--listen* options for *mu scm**.
|
||||||
|
|
||||||
|
The store object (including the Xapian database) is shared between the server and the REPL.
|
||||||
|
|
||||||
* PERFORMANCE
|
* PERFORMANCE
|
||||||
|
|
||||||
As an indication for the relative performance, we can simulate something ~mu4e~
|
As an indication for the relative performance, we can simulate something ~mu4e~
|
||||||
does; we take overall time of 50 such requests:
|
does. We take the overall time of 50 such requests:
|
||||||
|
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-related t :threads t :maxnum 50000)' >/dev/null
|
time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-related t :threads t :maxnum 50000)' >/dev/null
|
||||||
@ -78,7 +84,6 @@ time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-r
|
|||||||
| 1.10 | 5.7s |
|
| 1.10 | 5.7s |
|
||||||
| 1.11 (master) | 2.8s |
|
| 1.11 (master) | 2.8s |
|
||||||
|
|
||||||
|
|
||||||
#+include: "muhome.inc" :minlevel 2
|
#+include: "muhome.inc" :minlevel 2
|
||||||
|
|
||||||
#+include: "common-options.inc" :minlevel 1
|
#+include: "common-options.inc" :minlevel 1
|
||||||
@ -87,4 +92,5 @@ time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-r
|
|||||||
|
|
||||||
* SEE ALSO
|
* SEE ALSO
|
||||||
|
|
||||||
{{{man-link(mu,1)}}}
|
{{{man-link(mu,1)}}},
|
||||||
|
{{{man-link(mu-scm,1)}}}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** Copyright (C) 2020-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
** Copyright (C) 2020-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
**
|
**
|
||||||
** This program is free software; you can redistribute it and/or modify it
|
** This program is free software; you can redistribute it and/or modify it
|
||||||
** under the terms of the GNU General Public License as published by the
|
** under the terms of the GNU General Public License as published by the
|
||||||
@ -29,6 +29,10 @@
|
|||||||
#include "mu-cmd.hh"
|
#include "mu-cmd.hh"
|
||||||
#include "mu-server.hh"
|
#include "mu-server.hh"
|
||||||
|
|
||||||
|
#if BUILD_SCM
|
||||||
|
#include "scm/mu-scm.hh"
|
||||||
|
#endif/*BUILD_SCM*/
|
||||||
|
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
#include "utils/mu-readline.hh"
|
#include "utils/mu-readline.hh"
|
||||||
|
|
||||||
@ -60,6 +64,8 @@ install_sig_handler()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* djcb@djcbsoftware.nl
|
||||||
|
*
|
||||||
* Markers for/after the length cookie that precedes the expression we write to
|
* Markers for/after the length cookie that precedes the expression we write to
|
||||||
* output. We use octal 376, 377 (ie, 0xfe, 0xff) as they will never occur in
|
* output. We use octal 376, 377 (ie, 0xfe, 0xff) as they will never occur in
|
||||||
* utf8
|
* utf8
|
||||||
@ -102,6 +108,46 @@ report_error(const Mu::Error& err) noexcept
|
|||||||
Server::OutputFlags::Flush);
|
Server::OutputFlags::Flush);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
maybe_setup_readline(const Mu::Options& opts)
|
||||||
|
{
|
||||||
|
tty = ::isatty(::fileno(stdout));
|
||||||
|
|
||||||
|
// Note, the readline stuff is inactive unless on a tty.
|
||||||
|
const auto histpath{opts.runtime_path(RuntimePath::Cache) + "/history"};
|
||||||
|
setup_readline(histpath, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the SCM socket path we're listening on, or empty if not listening
|
||||||
|
static std::string
|
||||||
|
maybe_listen_path(const Mu::Store& store, const Mu::Options& opts)
|
||||||
|
{
|
||||||
|
#ifdef BUILD_SCM
|
||||||
|
if (!opts.scm.socket_path)
|
||||||
|
return {};
|
||||||
|
const auto res = Mu::Scm::run(store, opts, false/*!block*/);
|
||||||
|
if (!res) {
|
||||||
|
mu_warning("failed to start scm socket: {}", res.error().what());
|
||||||
|
return {};
|
||||||
|
} else
|
||||||
|
return *opts.scm.socket_path;
|
||||||
|
#endif /*BUILD_SCM*/
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
maybe_eval(Server& server, const Mu::Options& opts)
|
||||||
|
{
|
||||||
|
const auto eval = std::string{opts.server.commands ?
|
||||||
|
"(help :full t)" : opts.server.eval};
|
||||||
|
|
||||||
|
if (!eval.empty()) {
|
||||||
|
server.invoke(eval);
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Result<void>
|
Result<void>
|
||||||
Mu::mu_cmd_server(const Mu::Options& opts) try {
|
Mu::mu_cmd_server(const Mu::Options& opts) try {
|
||||||
|
|
||||||
@ -110,31 +156,23 @@ Mu::mu_cmd_server(const Mu::Options& opts) try {
|
|||||||
if (!store)
|
if (!store)
|
||||||
return Err(store.error());
|
return Err(store.error());
|
||||||
|
|
||||||
Server::Options sopts{};
|
// empty when we're not listening
|
||||||
sopts.allow_temp_file = opts.server.allow_temp_file;
|
const auto socket_path = maybe_listen_path(*store, opts);
|
||||||
|
|
||||||
|
Server::Options sopts{opts.server.allow_temp_file, socket_path};
|
||||||
Server server{*store, sopts, output_stdout};
|
Server server{*store, sopts, output_stdout};
|
||||||
mu_message("created server with store @ {}; maildir @ {}; debug-mode {};"
|
|
||||||
"readline: {}",
|
|
||||||
store->path(), store->root_maildir(),
|
|
||||||
opts.debug ? "yes" : "no",
|
|
||||||
have_readline() ? "yes" : "no");
|
|
||||||
|
|
||||||
tty = ::isatty(::fileno(stdout));
|
if (maybe_eval(server, opts))
|
||||||
const auto eval = std::string{opts.server.commands ? "(help :full t)" : opts.server.eval};
|
|
||||||
if (!eval.empty()) {
|
|
||||||
server.invoke(eval);
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
|
||||||
|
|
||||||
// Note, the readline stuff is inactive unless on a tty.
|
|
||||||
const auto histpath{opts.runtime_path(RuntimePath::Cache) + "/history"};
|
|
||||||
setup_readline(histpath, 50);
|
|
||||||
|
|
||||||
|
maybe_setup_readline(opts);
|
||||||
install_sig_handler();
|
install_sig_handler();
|
||||||
mu_println(";; Welcome to the " PACKAGE_STRING " command-server{}\n"
|
|
||||||
";; Use (help) to get a list of commands, (quit) to quit.",
|
mu_println(";; Welcome to the " PACKAGE_STRING " command-server{}",
|
||||||
opts.debug ? " (debug-mode)" : "");
|
opts.debug ? " (debug-mode)" : "");
|
||||||
|
if (!socket_path.empty())
|
||||||
|
mu_println(";; SCM socket listening on {}", socket_path);
|
||||||
|
mu_println(";; Use (help) to get a list of commands, (quit) to quit.");
|
||||||
|
|
||||||
bool do_quit{};
|
bool do_quit{};
|
||||||
while (!MuTerminate && !do_quit) {
|
while (!MuTerminate && !do_quit) {
|
||||||
@ -151,7 +189,6 @@ Mu::mu_cmd_server(const Mu::Options& opts) try {
|
|||||||
mu_message ("shutting down due to signal {}", MuTerminate.load());
|
mu_message ("shutting down due to signal {}", MuTerminate.load());
|
||||||
|
|
||||||
shutdown_readline();
|
shutdown_readline();
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|
||||||
} catch (const Error& er) { /* note: user-level error, "OK" for mu */
|
} catch (const Error& er) { /* note: user-level error, "OK" for mu */
|
||||||
|
|||||||
@ -616,6 +616,24 @@ sub_remove(CLI::App& sub, Options& opts)
|
|||||||
->type_name("<files>");
|
->type_name("<files>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string
|
||||||
|
concoct_socket_path()
|
||||||
|
{
|
||||||
|
return join_paths(g_get_user_runtime_dir(),
|
||||||
|
mu_format("mu-scm-{}.sock", ::getpid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_listen_flag(CLI::App& sub, Options& opts)
|
||||||
|
{
|
||||||
|
sub.add_flag("--listen", opts.scm.listen,
|
||||||
|
"Start SCM REPL on a domain socket");
|
||||||
|
sub.callback([&opts]{
|
||||||
|
if (opts.scm.listen)
|
||||||
|
opts.scm.socket_path = concoct_socket_path();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sub_server(CLI::App& sub, Options& opts)
|
sub_server(CLI::App& sub, Options& opts)
|
||||||
{
|
{
|
||||||
@ -627,18 +645,23 @@ sub_server(CLI::App& sub, Options& opts)
|
|||||||
sub.add_flag("--allow-temp-file", opts.server.allow_temp_file,
|
sub.add_flag("--allow-temp-file", opts.server.allow_temp_file,
|
||||||
"Allow for the temp-file optimization")
|
"Allow for the temp-file optimization")
|
||||||
->excludes("--commands");
|
->excludes("--commands");
|
||||||
|
|
||||||
|
#if BUILD_SCM
|
||||||
|
add_listen_flag(sub, opts);
|
||||||
|
#endif/*BUILD_SCM*/
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sub_scm(CLI::App& sub, Options& opts)
|
sub_scm(CLI::App& sub, Options& opts)
|
||||||
{
|
{
|
||||||
sub.add_flag("--listen", opts.scm.listen,
|
add_listen_flag(sub, opts);
|
||||||
"Start listening on a domain socket");
|
|
||||||
sub.add_option("script-path", opts.scm.script_path, "Path to script")
|
sub.add_option("script-path", opts.scm.script_path, "Path to script")
|
||||||
->type_name("<path>")
|
->type_name("<path>")
|
||||||
->excludes("--listen");
|
->excludes("--listen");
|
||||||
sub.add_option("script-args", opts.scm.params, "Parameters for script")
|
sub.add_option("script-args", opts.scm.params, "Parameters for script")
|
||||||
->type_name("<parameters>");
|
->type_name("<parameters>");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -787,7 +810,6 @@ AssocPairs<SubCommand, CommandInfo, Options::SubCommandNum> SubCommandInfos= {{
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static ScriptInfos
|
static ScriptInfos
|
||||||
add_scripts(CLI::App& app, Options& opts)
|
add_scripts(CLI::App& app, Options& opts)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -260,6 +260,8 @@ struct Options {
|
|||||||
OptString script_path; /**< Path to script (optional) */
|
OptString script_path; /**< Path to script (optional) */
|
||||||
StringVec params; /**< Parameters for script (after "--") */
|
StringVec params; /**< Parameters for script (after "--") */
|
||||||
bool listen; /**< Whether to start listening on a socket */
|
bool listen; /**< Whether to start listening on a socket */
|
||||||
|
Option<std::string> socket_path; /**< path for the '--listen'
|
||||||
|
* Unix domain socket */
|
||||||
} scm;
|
} scm;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
26
mu/mu.cc
26
mu/mu.cc
@ -77,6 +77,10 @@ handle_result(const Result<void>& res, const Mu::Options& opts)
|
|||||||
return res.error().exit_code();
|
return res.error().exit_code();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Options opts; // it's big, don't want it on the stack
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char* argv[]) try
|
main(int argc, char* argv[]) try
|
||||||
{
|
{
|
||||||
@ -93,34 +97,36 @@ main(int argc, char* argv[]) try
|
|||||||
/*
|
/*
|
||||||
* read command-line options
|
* read command-line options
|
||||||
*/
|
*/
|
||||||
const auto opts{Options::make(argc, argv)};
|
auto optsres{Options::make(argc, argv)};
|
||||||
if (!opts) {
|
if (!optsres) {
|
||||||
output_error(opts.error().what(), !Options::default_no_color());
|
output_error(optsres.error().what(), !Options::default_no_color());
|
||||||
return opts.error().exit_code();
|
return optsres.error().exit_code();
|
||||||
} else if (!opts->sub_command) {
|
} else if (!optsres->sub_command) {
|
||||||
// nothing more to do.
|
// nothing more to do.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts = std::move(*optsres);
|
||||||
|
|
||||||
// setup logging
|
// setup logging
|
||||||
Logger::Options lopts{Logger::Options::None};
|
Logger::Options lopts{Logger::Options::None};
|
||||||
if (opts->log_stderr)
|
if (opts.log_stderr)
|
||||||
lopts |= Logger::Options::StdOutErr;
|
lopts |= Logger::Options::StdOutErr;
|
||||||
if (opts->debug)
|
if (opts.debug)
|
||||||
lopts |= Logger::Options::Debug;
|
lopts |= Logger::Options::Debug;
|
||||||
if (!!g_getenv("MU_TEST"))
|
if (!!g_getenv("MU_TEST"))
|
||||||
lopts |= Logger::Options::File;
|
lopts |= Logger::Options::File;
|
||||||
|
|
||||||
const auto logger{Logger::make(opts->runtime_path(RuntimePath::LogFile), lopts)};
|
const auto logger{Logger::make(opts.runtime_path(RuntimePath::LogFile), lopts)};
|
||||||
if (!logger) {
|
if (!logger) {
|
||||||
output_error(logger.error().what(), !opts->nocolor);
|
output_error(logger.error().what(), !opts.nocolor);
|
||||||
return logger.error().exit_code();
|
return logger.error().exit_code();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* handle sub command
|
* handle sub command
|
||||||
*/
|
*/
|
||||||
return handle_result(mu_cmd_execute(*opts), *opts);
|
return handle_result(mu_cmd_execute(opts), opts);
|
||||||
|
|
||||||
// exceptions should have been handled earlier, but catch them here,
|
// exceptions should have been handled earlier, but catch them here,
|
||||||
// just in case...
|
// just in case...
|
||||||
|
|||||||
Reference in New Issue
Block a user