From 1fe0c673bc3ead4e99e47e61e7aa3f0ed590bbba Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Wed, 10 Mar 2021 20:25:20 +0100
Subject: [PATCH] output/wasapi: implement Cancel() properly

Calling consume_all() is illegal in the producer thread.
---
 .../plugins/wasapi/WasapiOutputPlugin.cxx     | 29 ++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
index ba6b61a80..e7075c0b5 100644
--- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
+++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
@@ -170,7 +170,10 @@ class WasapiOutputThread {
 
 	bool started = false;
 
+	std::atomic_bool cancel = false;
+
 	enum class Status : uint32_t { FINISH, PLAY, PAUSE };
+
 	alignas(BOOST_LOCKFREE_CACHELINE_BYTES) std::atomic<Status> status =
 		Status::PAUSE;
 	alignas(BOOST_LOCKFREE_CACHELINE_BYTES) struct {
@@ -201,6 +204,24 @@ public:
 	void Play() noexcept { return SetStatus(Status::PLAY); }
 	void Pause() noexcept { return SetStatus(Status::PAUSE); }
 
+	/**
+	 * Instruct the thread to discard the buffer (and wait for
+	 * completion).  This needs to be done inside this thread,
+	 * because only the consumer thread is allowed to do that.
+	 */
+	void Cancel() noexcept {
+		cancel.store(true);
+		event.Set();
+
+		while (cancel.load() && !error.occur.load())
+			Wait();
+
+		/* not rethrowing the exception here via
+		   CheckException() because this method must be
+		   "noexcept"; the next WasapiOutput::Play() call will
+		   throw */
+	}
+
 	/**
 	 * Wait for the thread to finish some work (e.g. until some
 	 * buffer space becomes available).
@@ -366,6 +387,12 @@ try {
 	while (true) {
 		event.Wait();
 
+		if (cancel.load()) {
+			spsc_buffer.consume_all([](auto &&) {});
+			cancel.store(false);
+			InterruptWaiter();
+		}
+
 		Status current_state = status.load();
 		switch (current_state) {
 		case Status::FINISH:
@@ -707,7 +734,7 @@ WasapiOutput::Cancel() noexcept
 {
 	assert(thread);
 
-	thread->spsc_buffer.consume_all([](auto &&) {});
+	thread->Cancel();
 }
 
 /// run inside COMWorkerThread