summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2019-04-03 14:31:57 +0200
committerMax Kellermann <max@musicpd.org>2019-04-05 11:18:15 +0200
commit9f1c23e21766337d46dae5dce9be5e5975bf1cfa (patch)
tree038d0316181d1bf741e01327c074cb31c89aa9c8 /src/client
parent28fc1d555fe026fbcc9c8ea86c783316bdbafed8 (diff)
client/BackgroundCommand: infrastructure for commands running in background
Diffstat (limited to 'src/client')
-rw-r--r--src/client/BackgroundCommand.hxx47
-rw-r--r--src/client/Client.cxx33
-rw-r--r--src/client/Client.hxx23
-rw-r--r--src/client/Expire.cxx1
-rw-r--r--src/client/New.cxx1
-rw-r--r--src/client/Process.cxx2
-rw-r--r--src/client/Read.cxx4
-rw-r--r--src/client/ThreadBackgroundCommand.cxx76
-rw-r--r--src/client/ThreadBackgroundCommand.hxx79
9 files changed, 266 insertions, 0 deletions
diff --git a/src/client/BackgroundCommand.hxx b/src/client/BackgroundCommand.hxx
new file mode 100644
index 000000000..8760fddc1
--- /dev/null
+++ b/src/client/BackgroundCommand.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2003-2019 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BACKGROUND_COMMAND_HXX
+#define MPD_BACKGROUND_COMMAND_HXX
+
+/**
+ * A command running in background. It can take some time to finish,
+ * and will then call Client::OnBackgroundCommandFinished() from
+ * inside the client's #EventLoop thread. The important point is that
+ * sucha long-running command does not block MPD's main loop, and
+ * other clients can still be handled meanwhile.
+ *
+ * (Note: "idle" is not a "background command" by this definition; it
+ * is a special case.)
+ *
+ * @see ThreadBackgroundCommand
+ */
+class BackgroundCommand {
+public:
+ virtual ~BackgroundCommand() = default;
+
+ /**
+ * Cancel command execution. After this method returns, the
+ * object will be deleted. It will be called from the
+ * #Client's #EventLoop thread.
+ */
+ virtual void Cancel() noexcept = 0;
+};
+
+#endif
diff --git a/src/client/Client.cxx b/src/client/Client.cxx
index 2bb326bf8..333346cb6 100644
--- a/src/client/Client.cxx
+++ b/src/client/Client.cxx
@@ -18,8 +18,10 @@
*/
#include "Client.hxx"
+#include "Config.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
+#include "BackgroundCommand.hxx"
#include "util/Domain.hxx"
#include "config.h"
@@ -27,6 +29,37 @@ Client::~Client() noexcept
{
if (FullyBufferedSocket::IsDefined())
FullyBufferedSocket::Close();
+
+ if (background_command) {
+ background_command->Cancel();
+ background_command.reset();
+ }
+}
+
+void
+Client::SetBackgroundCommand(std::unique_ptr<BackgroundCommand> _bc) noexcept
+{
+ assert(!background_command);
+ assert(_bc);
+
+ background_command = std::move(_bc);
+
+ /* disable timeouts while in "idle" */
+ timeout_event.Cancel();
+}
+
+void
+Client::OnBackgroundCommandFinished() noexcept
+{
+ assert(background_command);
+
+ background_command.reset();
+
+ /* just in case OnSocketInput() has returned
+ InputResult::PAUSE meanwhile */
+ ResumeInput();
+
+ timeout_event.Schedule(client_timeout);
}
Instance &
diff --git a/src/client/Client.hxx b/src/client/Client.hxx
index 753f0a04e..11ee0ed00 100644
--- a/src/client/Client.hxx
+++ b/src/client/Client.hxx
@@ -34,6 +34,7 @@
#include <set>
#include <string>
#include <list>
+#include <memory>
#include <stddef.h>
@@ -47,6 +48,7 @@ class PlayerControl;
struct playlist;
class Database;
class Storage;
+class BackgroundCommand;
class Client final
: FullyBufferedSocket,
@@ -102,6 +104,14 @@ private:
*/
std::list<ClientMessage> messages;
+ /**
+ * The command currently running in background. If this is
+ * set, then the client is occupied and will not process any
+ * new input. If the connection gets closed, the
+ * #BackgroundCommand will be cancelled.
+ */
+ std::unique_ptr<BackgroundCommand> background_command;
+
public:
Client(EventLoop &loop, Partition &partition,
UniqueSocketDescriptor fd, int uid,
@@ -152,6 +162,19 @@ public:
void IdleAdd(unsigned flags) noexcept;
bool IdleWait(unsigned flags) noexcept;
+ /**
+ * Called by a command handler to defer execution to a
+ * #BackgroundCommand.
+ */
+ void SetBackgroundCommand(std::unique_ptr<BackgroundCommand> _bc) noexcept;
+
+ /**
+ * Called by the current #BackgroundCommand when it has
+ * finished, after sending the response. This method then
+ * deletes the #BackgroundCommand.
+ */
+ void OnBackgroundCommandFinished() noexcept;
+
enum class SubscribeResult {
/** success */
OK,
diff --git a/src/client/Expire.cxx b/src/client/Expire.cxx
index d6129bf5f..b52d780b8 100644
--- a/src/client/Expire.cxx
+++ b/src/client/Expire.cxx
@@ -25,6 +25,7 @@ void
Client::OnTimeout() noexcept
{
assert(!idle_waiting);
+ assert(!background_command);
FormatDebug(client_domain, "[%u] timeout", num);
diff --git a/src/client/New.cxx b/src/client/New.cxx
index 9ef1c5233..432e4076b 100644
--- a/src/client/New.cxx
+++ b/src/client/New.cxx
@@ -21,6 +21,7 @@
#include "Config.hxx"
#include "Domain.hxx"
#include "List.hxx"
+#include "BackgroundCommand.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "net/UniqueSocketDescriptor.hxx"
diff --git a/src/client/Process.cxx b/src/client/Process.cxx
index e138d2fa3..8966eca05 100644
--- a/src/client/Process.cxx
+++ b/src/client/Process.cxx
@@ -54,6 +54,8 @@ Client::ProcessCommandList(bool list_ok,
CommandResult
Client::ProcessLine(char *line) noexcept
{
+ assert(!background_command);
+
if (!IsLowerAlphaASCII(*line)) {
/* all valid MPD commands begin with a lower case
letter; this could be a badly routed HTTP
diff --git a/src/client/Read.cxx b/src/client/Read.cxx
index d7ada1bc1..191ba49f2 100644
--- a/src/client/Read.cxx
+++ b/src/client/Read.cxx
@@ -29,6 +29,9 @@
BufferedSocket::InputResult
Client::OnSocketInput(void *data, size_t length) noexcept
{
+ if (background_command)
+ return InputResult::PAUSE;
+
char *p = (char *)data;
char *newline = (char *)memchr(p, '\n', length);
if (newline == nullptr)
@@ -48,6 +51,7 @@ Client::OnSocketInput(void *data, size_t length) noexcept
switch (result) {
case CommandResult::OK:
case CommandResult::IDLE:
+ case CommandResult::BACKGROUND:
case CommandResult::ERROR:
break;
diff --git a/src/client/ThreadBackgroundCommand.cxx b/src/client/ThreadBackgroundCommand.cxx
new file mode 100644
index 000000000..32297c306
--- /dev/null
+++ b/src/client/ThreadBackgroundCommand.cxx
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2003-2019 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ThreadBackgroundCommand.hxx"
+#include "Client.hxx"
+#include "Response.hxx"
+#include "command/CommandError.hxx"
+#include "protocol/Result.hxx"
+
+ThreadBackgroundCommand::ThreadBackgroundCommand(Client &_client) noexcept
+ :thread(BIND_THIS_METHOD(_Run)),
+ defer_finish(_client.GetEventLoop(), BIND_THIS_METHOD(DeferredFinish)),
+ client(_client)
+{
+}
+
+void
+ThreadBackgroundCommand::_Run() noexcept
+{
+ assert(!error);
+
+ try {
+ Run();
+ } catch (...) {
+ error = std::current_exception();
+ }
+
+ defer_finish.Schedule();
+}
+
+void
+ThreadBackgroundCommand::DeferredFinish() noexcept
+{
+ /* free the Thread */
+ thread.Join();
+
+ /* send the response */
+ Response response(client, 0);
+
+ if (!error) {
+ PrintError(response, std::move(error));
+ } else {
+ SendResponse(response);
+ command_success(client);
+ }
+
+ /* delete this object */
+ client.OnBackgroundCommandFinished();
+}
+
+void
+ThreadBackgroundCommand::Cancel() noexcept
+{
+ CancelThread();
+ thread.Join();
+
+ /* cancel the DeferEvent, just in case the Thread has
+ meanwhile finished execution */
+ defer_finish.Cancel();
+}
diff --git a/src/client/ThreadBackgroundCommand.hxx b/src/client/ThreadBackgroundCommand.hxx
new file mode 100644
index 000000000..ff55b3bcd
--- /dev/null
+++ b/src/client/ThreadBackgroundCommand.hxx
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2003-2019 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_THREAD_BACKGROUND_COMMAND_HXX
+#define MPD_THREAD_BACKGROUND_COMMAND_HXX
+
+#include "BackgroundCommand.hxx"
+#include "event/DeferEvent.hxx"
+#include "thread/Thread.hxx"
+
+#include <exception>
+
+class Client;
+class Response;
+
+/**
+ * A #BackgroundCommand which defers execution into a new thread.
+ */
+class ThreadBackgroundCommand : public BackgroundCommand {
+ Thread thread;
+ DeferEvent defer_finish;
+ Client &client;
+
+ /**
+ * The error thrown by Run().
+ */
+ std::exception_ptr error;
+
+public:
+ explicit ThreadBackgroundCommand(Client &_client) noexcept;
+
+ auto &GetEventLoop() const noexcept {
+ return defer_finish.GetEventLoop();
+ }
+
+ void Start() {
+ thread.Start();
+ }
+
+ void Cancel() noexcept final;
+
+private:
+ void _Run() noexcept;
+ void DeferredFinish() noexcept;
+
+protected:
+ /**
+ * If this method throws, the exception will be converted to a
+ * MPD response, and SendResponse() will not be called.
+ */
+ virtual void Run() = 0;
+
+ /**
+ * Send the response after Run() has finished. Note that you
+ * must not send errors here; if an error occurs, Run() should
+ * throw an exception instead.
+ */
+ virtual void SendResponse(Response &response) noexcept = 0;
+
+ virtual void CancelThread() noexcept = 0;
+};
+
+#endif