From 4e0e4c00bffea303420911e4cfe31e35e775b78d Mon Sep 17 00:00:00 2001
From: Rosen Penev <rosenp@gmail.com>
Date: Thu, 11 Nov 2021 16:19:32 -0800
Subject: [PATCH 1/5] treewide: replace lock_guard with scoped_lock

SonarLint reports the latter to be better:

std::scoped_lock basically provides the same feature as std::lock_guard,
but is more generic: It can lock several mutexes at the same time, with a
deadlock prevention mechanism (see {rule:cpp:S5524}). The equivalent code
to perform simultaneous locking with std::lock_guard is significantly more
complex. Therefore, it is simpler to use std::scoped_lock all the time,
even when locking only one mutex (there will be no performance impact).

Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 src/MusicBuffer.cxx                           |  4 ++--
 src/MusicBuffer.hxx                           |  2 +-
 src/MusicPipe.cxx                             |  6 ++---
 src/MusicPipe.hxx                             |  4 ++--
 src/RemoteTagCache.cxx                        |  6 ++---
 src/command/FingerprintCommands.cxx           |  4 ++--
 src/db/update/Remove.cxx                      |  4 ++--
 src/decoder/Bridge.cxx                        |  8 +++----
 src/decoder/Control.hxx                       | 10 ++++-----
 src/decoder/Thread.cxx                        |  6 ++---
 src/event/Call.cxx                            |  2 +-
 src/event/Loop.cxx                            | 10 ++++-----
 src/input/AsyncInputStream.cxx                |  4 ++--
 src/input/BufferingInputStream.cxx            |  2 +-
 src/input/InputStream.cxx                     |  4 ++--
 src/input/ThreadInputStream.cxx               |  2 +-
 src/input/cache/Item.cxx                      |  4 ++--
 src/input/cache/Item.hxx                      |  2 +-
 src/input/cache/Stream.cxx                    |  8 +++----
 src/input/plugins/AlsaInputPlugin.cxx         |  2 +-
 src/input/plugins/CurlInputPlugin.cxx         |  8 +++----
 src/input/plugins/NfsInputPlugin.cxx          |  6 ++---
 src/input/plugins/QobuzClient.cxx             | 10 ++++-----
 src/input/plugins/QobuzClient.hxx             |  2 +-
 src/input/plugins/QobuzInputPlugin.cxx        |  6 ++---
 src/input/plugins/UringInputPlugin.cxx        |  4 ++--
 src/lib/curl/Init.cxx                         |  4 ++--
 src/lib/icu/Converter.cxx                     |  4 ++--
 src/lib/nfs/Blocking.hxx                      |  2 +-
 src/lib/smbclient/Context.cxx                 |  2 +-
 src/lib/smbclient/Context.hxx                 |  2 +-
 src/lib/upnp/ClientInit.cxx                   |  4 ++--
 src/lib/upnp/Discovery.cxx                    | 20 ++++++++---------
 src/lib/upnp/Init.cxx                         |  4 ++--
 src/mixer/MixerControl.cxx                    |  8 +++----
 .../plugins/SmbclientNeighborPlugin.cxx       |  4 ++--
 src/neighbor/plugins/UdisksNeighborPlugin.cxx |  4 ++--
 src/output/Control.cxx                        | 22 +++++++++----------
 src/output/Control.hxx                        |  4 ++--
 src/output/MultipleOutputs.cxx                |  2 +-
 src/output/State.cxx                          |  2 +-
 src/output/plugins/AlsaOutputPlugin.cxx       | 22 +++++++++----------
 src/output/plugins/JackOutputPlugin.cxx       |  8 +++----
 src/output/plugins/httpd/HttpdClient.cxx      |  4 ++--
 src/output/plugins/httpd/HttpdInternal.hxx    |  2 +-
 .../plugins/httpd/HttpdOutputPlugin.cxx       | 16 +++++++-------
 src/output/plugins/sles/SlesOutputPlugin.cxx  |  4 ++--
 src/output/plugins/snapcast/Client.cxx        |  4 ++--
 src/output/plugins/snapcast/Internal.hxx      |  2 +-
 .../plugins/snapcast/SnapcastOutputPlugin.cxx | 14 ++++++------
 src/player/Control.cxx                        |  8 +++----
 src/player/Control.hxx                        | 12 +++++-----
 src/player/Thread.cxx                         |  4 ++--
 src/storage/CompositeStorage.cxx              | 16 +++++++-------
 src/storage/CompositeStorage.hxx              |  4 ++--
 src/storage/plugins/CurlStorage.cxx           |  4 ++--
 src/storage/plugins/NfsStorage.cxx            |  4 ++--
 src/storage/plugins/SmbclientStorage.cxx      |  8 +++----
 src/storage/plugins/UdisksStorage.cxx         | 14 ++++++------
 src/tag/Builder.cxx                           | 10 ++++-----
 src/tag/Tag.cxx                               |  4 ++--
 src/thread/SafeSingleton.hxx                  |  4 ++--
 test/dump_text_file.cxx                       |  2 +-
 test/run_input.cxx                            |  4 ++--
 64 files changed, 196 insertions(+), 196 deletions(-)

diff --git a/src/MusicBuffer.cxx b/src/MusicBuffer.cxx
index 541d9499e..120aca0a2 100644
--- a/src/MusicBuffer.cxx
+++ b/src/MusicBuffer.cxx
@@ -29,7 +29,7 @@ MusicBuffer::MusicBuffer(unsigned num_chunks)
 MusicChunkPtr
 MusicBuffer::Allocate() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return MusicChunkPtr(buffer.Allocate(), MusicChunkDeleter(*this));
 }
 
@@ -44,7 +44,7 @@ MusicBuffer::Return(MusicChunk *chunk) noexcept
 	chunk->next.reset();
 	chunk->other.reset();
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	assert(!chunk->other || !chunk->other->other);
 
diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx
index 9d8ee2c0a..0d1aedd2b 100644
--- a/src/MusicBuffer.hxx
+++ b/src/MusicBuffer.hxx
@@ -54,7 +54,7 @@ public:
 #endif
 
 	bool IsFull() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return buffer.IsFull();
 	}
 
diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx
index 5de731e51..739ed90e0 100644
--- a/src/MusicPipe.cxx
+++ b/src/MusicPipe.cxx
@@ -27,7 +27,7 @@
 bool
 MusicPipe::Contains(const MusicChunk *chunk) const noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	for (const MusicChunk *i = head.get(); i != nullptr; i = i->next.get())
 		if (i == chunk)
@@ -41,7 +41,7 @@ MusicPipe::Contains(const MusicChunk *chunk) const noexcept
 MusicChunkPtr
 MusicPipe::Shift() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	auto chunk = std::move(head);
 	if (chunk != nullptr) {
@@ -81,7 +81,7 @@ MusicPipe::Push(MusicChunkPtr chunk) noexcept
 	assert(!chunk->IsEmpty());
 	assert(chunk->length == 0 || chunk->audio_format.IsValid());
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	assert(size > 0 || !audio_format.IsDefined());
 	assert(!audio_format.IsDefined() ||
diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx
index 68527391b..fc7c773a5 100644
--- a/src/MusicPipe.hxx
+++ b/src/MusicPipe.hxx
@@ -77,7 +77,7 @@ public:
 	 */
 	[[gnu::pure]]
 	const MusicChunk *Peek() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return head.get();
 	}
 
@@ -101,7 +101,7 @@ public:
 	 */
 	[[gnu::pure]]
 	unsigned GetSize() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return size;
 	}
 
diff --git a/src/RemoteTagCache.cxx b/src/RemoteTagCache.cxx
index 4019326e4..0759496b2 100644
--- a/src/RemoteTagCache.cxx
+++ b/src/RemoteTagCache.cxx
@@ -98,7 +98,7 @@ RemoteTagCache::ItemResolved(Item &item) noexcept
 void
 RemoteTagCache::InvokeHandlers() noexcept
 {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 
 	while (!invoke_list.empty()) {
 		auto &item = invoke_list.front();
@@ -125,7 +125,7 @@ RemoteTagCache::Item::OnRemoteTag(Tag &&_tag) noexcept
 
 	scanner.reset();
 
-	const std::lock_guard<Mutex> lock(parent.mutex);
+	const std::scoped_lock<Mutex> lock(parent.mutex);
 	parent.ItemResolved(*this);
 }
 
@@ -137,6 +137,6 @@ RemoteTagCache::Item::OnRemoteTagError(std::exception_ptr e) noexcept
 
 	scanner.reset();
 
-	const std::lock_guard<Mutex> lock(parent.mutex);
+	const std::scoped_lock<Mutex> lock(parent.mutex);
 	parent.ItemResolved(*this);
 }
diff --git a/src/command/FingerprintCommands.cxx b/src/command/FingerprintCommands.cxx
index 490c25f9c..ff91f6f57 100644
--- a/src/command/FingerprintCommands.cxx
+++ b/src/command/FingerprintCommands.cxx
@@ -67,7 +67,7 @@ protected:
 	}
 
 	void CancelThread() noexcept override {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		cancel = true;
 		cond.notify_one();
 	}
@@ -204,7 +204,7 @@ GetChromaprintCommand::DecodeFile(std::string_view suffix, InputStream &is,
 		return false;
 
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		if (cancel)
 			throw StopDecoder();
 	}
diff --git a/src/db/update/Remove.cxx b/src/db/update/Remove.cxx
index e65e1cc0b..250e83fee 100644
--- a/src/db/update/Remove.cxx
+++ b/src/db/update/Remove.cxx
@@ -36,7 +36,7 @@ UpdateRemoveService::RunDeferred() noexcept
 	std::forward_list<std::string> copy;
 
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		std::swap(uris, copy);
 	}
 
@@ -55,7 +55,7 @@ UpdateRemoveService::Remove(std::string &&uri)
 	bool was_empty;
 
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		was_empty = uris.empty();
 		uris.emplace_front(std::move(uri));
 	}
diff --git a/src/decoder/Bridge.cxx b/src/decoder/Bridge.cxx
index 81b8b8f8e..a57f8d8cc 100644
--- a/src/decoder/Bridge.cxx
+++ b/src/decoder/Bridge.cxx
@@ -152,7 +152,7 @@ DecoderBridge::FlushChunk() noexcept
 	if (!chunk->IsEmpty())
 		dc.pipe->Push(std::move(chunk));
 
-	const std::lock_guard<Mutex> protect(dc.mutex);
+	const std::scoped_lock<Mutex> protect(dc.mutex);
 	dc.client_cond.notify_one();
 }
 
@@ -214,7 +214,7 @@ DecoderBridge::GetVirtualCommand() noexcept
 DecoderCommand
 DecoderBridge::LockGetVirtualCommand() noexcept
 {
-	const std::lock_guard<Mutex> protect(dc.mutex);
+	const std::scoped_lock<Mutex> protect(dc.mutex);
 	return GetVirtualCommand();
 }
 
@@ -274,7 +274,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
 		 seekable);
 
 	{
-		const std::lock_guard<Mutex> protect(dc.mutex);
+		const std::scoped_lock<Mutex> protect(dc.mutex);
 		dc.SetReady(audio_format, seekable, duration);
 	}
 
@@ -300,7 +300,7 @@ DecoderBridge::GetCommand() noexcept
 void
 DecoderBridge::CommandFinished() noexcept
 {
-	const std::lock_guard<Mutex> protect(dc.mutex);
+	const std::scoped_lock<Mutex> protect(dc.mutex);
 
 	assert(dc.command != DecoderCommand::NONE || initial_seek_running);
 	assert(dc.command != DecoderCommand::SEEK ||
diff --git a/src/decoder/Control.hxx b/src/decoder/Control.hxx
index 0a02f2066..cad64a81e 100644
--- a/src/decoder/Control.hxx
+++ b/src/decoder/Control.hxx
@@ -231,7 +231,7 @@ public:
 
 	[[gnu::pure]]
 	bool LockIsIdle() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return IsIdle();
 	}
 
@@ -241,7 +241,7 @@ public:
 
 	[[gnu::pure]]
 	bool LockIsStarting() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return IsStarting();
 	}
 
@@ -253,7 +253,7 @@ public:
 
 	[[gnu::pure]]
 	bool LockHasFailed() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return HasFailed();
 	}
 
@@ -284,7 +284,7 @@ public:
 	 * Like CheckRethrowError(), but locks and unlocks the object.
 	 */
 	void LockCheckRethrowError() const {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		CheckRethrowError();
 	}
 
@@ -360,7 +360,7 @@ private:
 	}
 
 	void LockAsynchronousCommand(DecoderCommand cmd) noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		command = cmd;
 		Signal();
 	}
diff --git a/src/decoder/Thread.cxx b/src/decoder/Thread.cxx
index 273b77df7..b302dab72 100644
--- a/src/decoder/Thread.cxx
+++ b/src/decoder/Thread.cxx
@@ -262,7 +262,7 @@ static void
 MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
 {
 	{
-		const std::lock_guard<Mutex> protect(bridge.dc.mutex);
+		const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
 		if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
 			/* ReplayGain is disabled */
 			return;
@@ -337,7 +337,7 @@ TryDecoderFile(DecoderBridge &bridge, Path path_fs, std::string_view suffix,
 	DecoderControl &dc = bridge.dc;
 
 	if (plugin.file_decode != nullptr) {
-		const std::lock_guard<Mutex> protect(dc.mutex);
+		const std::scoped_lock<Mutex> protect(dc.mutex);
 		return decoder_file_decode(plugin, bridge, path_fs);
 	} else if (plugin.stream_decode != nullptr) {
 		std::unique_lock<Mutex> lock(dc.mutex);
@@ -365,7 +365,7 @@ TryContainerDecoder(DecoderBridge &bridge, Path path_fs,
 	bridge.Reset();
 
 	DecoderControl &dc = bridge.dc;
-	const std::lock_guard<Mutex> protect(dc.mutex);
+	const std::scoped_lock<Mutex> protect(dc.mutex);
 	return decoder_file_decode(plugin, bridge, path_fs);
 }
 
diff --git a/src/event/Call.cxx b/src/event/Call.cxx
index 06c3bbe1d..db34b29a7 100644
--- a/src/event/Call.cxx
+++ b/src/event/Call.cxx
@@ -68,7 +68,7 @@ private:
 			exception = std::current_exception();
 		}
 
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		done = true;
 		cond.notify_one();
 	}
diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index cfc136a5c..be0f1804b 100644
--- a/src/event/Loop.cxx
+++ b/src/event/Loop.cxx
@@ -323,7 +323,7 @@ EventLoop::Run() noexcept
 		/* try to handle DeferEvents without WakeFD
 		   overhead */
 		{
-			const std::lock_guard<Mutex> lock(mutex);
+			const std::scoped_lock<Mutex> lock(mutex);
 			HandleInject();
 #endif
 
@@ -346,7 +346,7 @@ EventLoop::Run() noexcept
 
 #ifdef HAVE_THREADED_EVENT_LOOP
 		{
-			const std::lock_guard<Mutex> lock(mutex);
+			const std::scoped_lock<Mutex> lock(mutex);
 			busy = true;
 		}
 #endif
@@ -378,7 +378,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
 	bool must_wake;
 
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		if (d.IsPending())
 			return;
 
@@ -397,7 +397,7 @@ EventLoop::AddInject(InjectEvent &d) noexcept
 void
 EventLoop::RemoveInject(InjectEvent &d) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (d.IsPending())
 		inject.erase(inject.iterator_to(d));
@@ -424,7 +424,7 @@ EventLoop::OnSocketReady([[maybe_unused]] unsigned flags) noexcept
 
 	wake_fd.Read();
 
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	HandleInject();
 }
 
diff --git a/src/input/AsyncInputStream.cxx b/src/input/AsyncInputStream.cxx
index 62d90d1ae..38ae566ee 100644
--- a/src/input/AsyncInputStream.cxx
+++ b/src/input/AsyncInputStream.cxx
@@ -244,7 +244,7 @@ AsyncInputStream::AppendToBuffer(const void *data, size_t append_size) noexcept
 void
 AsyncInputStream::DeferredResume() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	try {
 		Resume();
@@ -257,7 +257,7 @@ AsyncInputStream::DeferredResume() noexcept
 void
 AsyncInputStream::DeferredSeek() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	if (seek_state != SeekState::SCHEDULED)
 		return;
 
diff --git a/src/input/BufferingInputStream.cxx b/src/input/BufferingInputStream.cxx
index 6f27ba366..065dc14d8 100644
--- a/src/input/BufferingInputStream.cxx
+++ b/src/input/BufferingInputStream.cxx
@@ -37,7 +37,7 @@ BufferingInputStream::BufferingInputStream(InputStreamPtr _input)
 BufferingInputStream::~BufferingInputStream() noexcept
 {
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		stop = true;
 		wake_cond.notify_one();
 	}
diff --git a/src/input/InputStream.cxx b/src/input/InputStream.cxx
index 322138914..c7859ec0b 100644
--- a/src/input/InputStream.cxx
+++ b/src/input/InputStream.cxx
@@ -97,7 +97,7 @@ InputStream::ReadTag() noexcept
 std::unique_ptr<Tag>
 InputStream::LockReadTag() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return ReadTag();
 }
 
@@ -152,7 +152,7 @@ InputStream::LockReadFull(void *ptr, size_t _size)
 bool
 InputStream::LockIsEOF() const noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return IsEOF();
 }
 
diff --git a/src/input/ThreadInputStream.cxx b/src/input/ThreadInputStream.cxx
index c682b0781..030bb9ad8 100644
--- a/src/input/ThreadInputStream.cxx
+++ b/src/input/ThreadInputStream.cxx
@@ -45,7 +45,7 @@ ThreadInputStream::Stop() noexcept
 		return;
 
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		close = true;
 		wake_cond.notify_one();
 	}
diff --git a/src/input/cache/Item.cxx b/src/input/cache/Item.cxx
index 4a3bb903f..954811d22 100644
--- a/src/input/cache/Item.cxx
+++ b/src/input/cache/Item.cxx
@@ -37,14 +37,14 @@ InputCacheItem::~InputCacheItem() noexcept
 void
 InputCacheItem::AddLease(InputCacheLease &lease) noexcept
 {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	leases.push_back(lease);
 }
 
 void
 InputCacheItem::RemoveLease(InputCacheLease &lease) noexcept
 {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	auto i = leases.iterator_to(lease);
 	if (i == next_lease)
 		++next_lease;
diff --git a/src/input/cache/Item.hxx b/src/input/cache/Item.hxx
index d32116d21..d0f8ef97c 100644
--- a/src/input/cache/Item.hxx
+++ b/src/input/cache/Item.hxx
@@ -63,7 +63,7 @@ public:
 	using BufferingInputStream::size;
 
 	bool IsInUse() const noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		return !leases.empty();
 	}
 
diff --git a/src/input/cache/Stream.cxx b/src/input/cache/Stream.cxx
index c1d45137f..98880ccfa 100644
--- a/src/input/cache/Stream.cxx
+++ b/src/input/cache/Stream.cxx
@@ -36,7 +36,7 @@ CacheInputStream::Check()
 	const ScopeUnlock unlock(mutex);
 
 	auto &i = GetCacheItem();
-	const std::lock_guard<Mutex> protect(i.mutex);
+	const std::scoped_lock<Mutex> protect(i.mutex);
 
 	i.Check();
 }
@@ -60,7 +60,7 @@ CacheInputStream::IsAvailable() const noexcept
 	const ScopeUnlock unlock(mutex);
 
 	auto &i = GetCacheItem();
-	const std::lock_guard<Mutex> protect(i.mutex);
+	const std::scoped_lock<Mutex> protect(i.mutex);
 
 	return i.IsAvailable(_offset);
 }
@@ -76,7 +76,7 @@ CacheInputStream::Read(std::unique_lock<Mutex> &lock,
 
 	{
 		const ScopeUnlock unlock(mutex);
-		const std::lock_guard<Mutex> protect(i.mutex);
+		const std::scoped_lock<Mutex> protect(i.mutex);
 
 		nbytes = i.Read(lock, _offset, ptr, read_size);
 	}
@@ -91,6 +91,6 @@ CacheInputStream::OnInputCacheAvailable() noexcept
 	auto &i = GetCacheItem();
 	const ScopeUnlock unlock(i.mutex);
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	InvokeOnAvailable();
 }
diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx
index b3e56c625..1d210b887 100644
--- a/src/input/plugins/AlsaInputPlugin.cxx
+++ b/src/input/plugins/AlsaInputPlugin.cxx
@@ -239,7 +239,7 @@ AlsaInputStream::DispatchSockets() noexcept
 {
 	non_block.DispatchSockets(*this, capture_handle);
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	auto w = PrepareWriteBuffer();
 	const snd_pcm_uframes_t w_frames = w.size / frame_size;
diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
index 270c564c8..95f745963 100644
--- a/src/input/plugins/CurlInputPlugin.cxx
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -238,7 +238,7 @@ CurlInputStream::OnHeaders(unsigned status,
 				      StringFormat<40>("got HTTP status %u",
 						       status).c_str());
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (IsSeekPending()) {
 		/* don't update metadata while seeking */
@@ -301,7 +301,7 @@ CurlInputStream::OnData(ConstBuffer<void> data)
 {
 	assert(data.size > 0);
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (IsSeekPending())
 		SeekDone();
@@ -317,7 +317,7 @@ CurlInputStream::OnData(ConstBuffer<void> data)
 void
 CurlInputStream::OnEnd()
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	InvokeOnAvailable();
 
 	AsyncInputStream::SetClosed();
@@ -326,7 +326,7 @@ CurlInputStream::OnEnd()
 void
 CurlInputStream::OnError(std::exception_ptr e) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	postponed_exception = std::move(e);
 
 	if (IsSeekPending())
diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx
index 591f62b51..315447345 100644
--- a/src/input/plugins/NfsInputPlugin.cxx
+++ b/src/input/plugins/NfsInputPlugin.cxx
@@ -141,7 +141,7 @@ NfsInputStream::DoSeek(offset_type new_offset)
 void
 NfsInputStream::OnNfsFileOpen(uint64_t _size) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (reconnecting) {
 		/* reconnect has succeeded */
@@ -161,7 +161,7 @@ NfsInputStream::OnNfsFileOpen(uint64_t _size) noexcept
 void
 NfsInputStream::OnNfsFileRead(const void *data, size_t data_size) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	assert(!IsBufferFull());
 	assert(IsBufferFull() == (GetBufferSpace() == 0));
 	AppendToBuffer(data, data_size);
@@ -174,7 +174,7 @@ NfsInputStream::OnNfsFileRead(const void *data, size_t data_size) noexcept
 void
 NfsInputStream::OnNfsFileError(std::exception_ptr &&e) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (IsPaused()) {
 		/* while we're paused, don't report this error to the
diff --git a/src/input/plugins/QobuzClient.cxx b/src/input/plugins/QobuzClient.cxx
index 92e74c588..294456a5a 100644
--- a/src/input/plugins/QobuzClient.cxx
+++ b/src/input/plugins/QobuzClient.cxx
@@ -87,7 +87,7 @@ QobuzClient::StartLogin()
 void
 QobuzClient::AddLoginHandler(QobuzSessionHandler &h) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	assert(!h.is_linked());
 
 	const bool was_empty = handlers.empty();
@@ -114,7 +114,7 @@ QobuzClient::AddLoginHandler(QobuzSessionHandler &h) noexcept
 QobuzSession
 QobuzClient::GetSession() const
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (error)
 		std::rethrow_exception(error);
@@ -129,7 +129,7 @@ void
 QobuzClient::OnQobuzLoginSuccess(QobuzSession &&_session) noexcept
 {
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		session = std::move(_session);
 		login_request.reset();
 	}
@@ -141,7 +141,7 @@ void
 QobuzClient::OnQobuzLoginError(std::exception_ptr _error) noexcept
 {
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		error = std::move(_error);
 		login_request.reset();
 	}
@@ -152,7 +152,7 @@ QobuzClient::OnQobuzLoginError(std::exception_ptr _error) noexcept
 void
 QobuzClient::InvokeHandlers() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	while (!handlers.empty()) {
 		auto &h = handlers.front();
 		handlers.pop_front();
diff --git a/src/input/plugins/QobuzClient.hxx b/src/input/plugins/QobuzClient.hxx
index 501a293b1..0fa00dce0 100644
--- a/src/input/plugins/QobuzClient.hxx
+++ b/src/input/plugins/QobuzClient.hxx
@@ -83,7 +83,7 @@ public:
 	void AddLoginHandler(QobuzSessionHandler &h) noexcept;
 
 	void RemoveLoginHandler(QobuzSessionHandler &h) noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		if (h.is_linked())
 			h.unlink();
 	}
diff --git a/src/input/plugins/QobuzInputPlugin.cxx b/src/input/plugins/QobuzInputPlugin.cxx
index fe902c90b..29627cc53 100644
--- a/src/input/plugins/QobuzInputPlugin.cxx
+++ b/src/input/plugins/QobuzInputPlugin.cxx
@@ -84,7 +84,7 @@ private:
 void
 QobuzInputStream::OnQobuzSession() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	try {
 		const auto session = qobuz_client->GetSession();
@@ -103,7 +103,7 @@ QobuzInputStream::OnQobuzSession() noexcept
 void
 QobuzInputStream::OnQobuzTrackSuccess(std::string url) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	track_request.reset();
 
 	try {
@@ -117,7 +117,7 @@ QobuzInputStream::OnQobuzTrackSuccess(std::string url) noexcept
 void
 QobuzInputStream::OnQobuzTrackError(std::exception_ptr e) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	track_request.reset();
 
 	Failed(e);
diff --git a/src/input/plugins/UringInputPlugin.cxx b/src/input/plugins/UringInputPlugin.cxx
index 8c323554a..8b8ef06cb 100644
--- a/src/input/plugins/UringInputPlugin.cxx
+++ b/src/input/plugins/UringInputPlugin.cxx
@@ -149,7 +149,7 @@ UringInputStream::OnRead(std::unique_ptr<std::byte[]> data,
 {
 	read_operation.reset();
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (nbytes == 0) {
 		postponed_exception = std::make_exception_ptr(std::runtime_error("Premature end of file"));
@@ -170,7 +170,7 @@ UringInputStream::OnReadError(int error) noexcept
 {
 	read_operation.reset();
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	postponed_exception = std::make_exception_ptr(MakeErrno(error, "Read failed"));
 	InvokeOnAvailable();
diff --git a/src/lib/curl/Init.cxx b/src/lib/curl/Init.cxx
index 4fa067bc3..aa6e09efd 100644
--- a/src/lib/curl/Init.cxx
+++ b/src/lib/curl/Init.cxx
@@ -40,7 +40,7 @@ CurlGlobal *CurlInit::instance;
 
 CurlInit::CurlInit(EventLoop &event_loop)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	if (++ref > 1) {
 		assert(&event_loop == &instance->GetEventLoop());
 		return;
@@ -56,7 +56,7 @@ CurlInit::CurlInit(EventLoop &event_loop)
 
 CurlInit::~CurlInit() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	if (--ref > 0)
 		return;
 
diff --git a/src/lib/icu/Converter.cxx b/src/lib/icu/Converter.cxx
index 24c02a639..335a8f056 100644
--- a/src/lib/icu/Converter.cxx
+++ b/src/lib/icu/Converter.cxx
@@ -105,7 +105,7 @@ AllocatedString
 IcuConverter::ToUTF8(std::string_view s) const
 {
 #ifdef HAVE_ICU
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	ucnv_resetToUnicode(converter);
 
@@ -133,7 +133,7 @@ AllocatedString
 IcuConverter::FromUTF8(std::string_view s) const
 {
 #ifdef HAVE_ICU
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	const auto u = UCharFromUTF8(s);
 
diff --git a/src/lib/nfs/Blocking.hxx b/src/lib/nfs/Blocking.hxx
index fefe499a4..cccba2837 100644
--- a/src/lib/nfs/Blocking.hxx
+++ b/src/lib/nfs/Blocking.hxx
@@ -68,7 +68,7 @@ private:
 	 * thread.
 	 */
 	void LockSetFinished() noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		finished = true;
 		cond.notify_one();
 	}
diff --git a/src/lib/smbclient/Context.cxx b/src/lib/smbclient/Context.cxx
index 7ecdb5a4d..7ae77e2ab 100644
--- a/src/lib/smbclient/Context.cxx
+++ b/src/lib/smbclient/Context.cxx
@@ -45,7 +45,7 @@ SmbclientContext::New()
 	SMBCCTX *ctx;
 
 	{
-		const std::lock_guard<Mutex> protect(global_mutex);
+		const std::scoped_lock<Mutex> protect(global_mutex);
 		ctx = smbc_new_context();
 	}
 
diff --git a/src/lib/smbclient/Context.hxx b/src/lib/smbclient/Context.hxx
index 2a02a8e00..f4aa9598a 100644
--- a/src/lib/smbclient/Context.hxx
+++ b/src/lib/smbclient/Context.hxx
@@ -48,7 +48,7 @@ public:
 
 	~SmbclientContext() noexcept {
 		if (ctx != nullptr) {
-			const std::lock_guard<Mutex> protect(global_mutex);
+			const std::scoped_lock<Mutex> protect(global_mutex);
 			smbc_free_context(ctx, 1);
 		}
 	}
diff --git a/src/lib/upnp/ClientInit.cxx b/src/lib/upnp/ClientInit.cxx
index f6796d219..8e70e7841 100644
--- a/src/lib/upnp/ClientInit.cxx
+++ b/src/lib/upnp/ClientInit.cxx
@@ -62,7 +62,7 @@ UpnpClientGlobalInit(const char* iface)
 	UpnpGlobalInit(iface);
 
 	try {
-		const std::lock_guard<Mutex> protect(upnp_client_init_mutex);
+		const std::scoped_lock<Mutex> protect(upnp_client_init_mutex);
 		if (upnp_client_ref == 0)
 			DoInit();
 	} catch (...) {
@@ -78,7 +78,7 @@ void
 UpnpClientGlobalFinish() noexcept
 {
 	{
-		const std::lock_guard<Mutex> protect(upnp_client_init_mutex);
+		const std::scoped_lock<Mutex> protect(upnp_client_init_mutex);
 
 		assert(upnp_client_ref > 0);
 		if (--upnp_client_ref == 0)
diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx
index 1cc8f813a..b67328d49 100644
--- a/src/lib/upnp/Discovery.cxx
+++ b/src/lib/upnp/Discovery.cxx
@@ -41,14 +41,14 @@ UPnPDeviceDirectory::Downloader::Downloader(UPnPDeviceDirectory &_parent,
 	 expires(std::chrono::seconds(UpnpDiscovery_get_Expires(&disco))),
 	 request(*parent.curl, url.c_str(), *this)
 {
-	const std::lock_guard<Mutex> protect(parent.mutex);
+	const std::scoped_lock<Mutex> protect(parent.mutex);
 	parent.downloaders.push_back(*this);
 }
 
 void
 UPnPDeviceDirectory::Downloader::Destroy() noexcept
 {
-	const std::lock_guard<Mutex> protect(parent.mutex);
+	const std::scoped_lock<Mutex> protect(parent.mutex);
 	unlink();
 	delete this;
 }
@@ -139,7 +139,7 @@ AnnounceLostUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device)
 inline void
 UPnPDeviceDirectory::LockAdd(ContentDirectoryDescriptor &&d)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	for (auto &i : directories) {
 		if (i.id == d.id) {
@@ -157,7 +157,7 @@ UPnPDeviceDirectory::LockAdd(ContentDirectoryDescriptor &&d)
 inline void
 UPnPDeviceDirectory::LockRemove(const std::string &id)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	for (auto i = directories.begin(), end = directories.end();
 	     i != end; ++i) {
@@ -265,10 +265,10 @@ UPnPDeviceDirectory::UPnPDeviceDirectory(EventLoop &event_loop,
 
 UPnPDeviceDirectory::~UPnPDeviceDirectory() noexcept
 {
-	BlockingCall(GetEventLoop(), [this](){
-			const std::lock_guard<Mutex> protect(mutex);
-			downloaders.clear_and_dispose(DeleteDisposer());
-		});
+	BlockingCall(GetEventLoop(), [this]() {
+		const std::scoped_lock<Mutex> protect(mutex);
+		downloaders.clear_and_dispose(DeleteDisposer());
+	});
 }
 
 inline EventLoop &
@@ -308,7 +308,7 @@ UPnPDeviceDirectory::Search()
 std::vector<ContentDirectoryService>
 UPnPDeviceDirectory::GetDirectories()
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	ExpireDevices();
 
@@ -327,7 +327,7 @@ UPnPDeviceDirectory::GetDirectories()
 ContentDirectoryService
 UPnPDeviceDirectory::GetServer(std::string_view friendly_name)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	ExpireDevices();
 
diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx
index 29bbe2c98..bc3da2f51 100644
--- a/src/lib/upnp/Init.cxx
+++ b/src/lib/upnp/Init.cxx
@@ -56,7 +56,7 @@ DoInit(const char* iface)
 void
 UpnpGlobalInit(const char* iface)
 {
-	const std::lock_guard<Mutex> protect(upnp_init_mutex);
+	const std::scoped_lock<Mutex> protect(upnp_init_mutex);
 
 	if (upnp_ref == 0)
 		DoInit(iface);
@@ -67,7 +67,7 @@ UpnpGlobalInit(const char* iface)
 void
 UpnpGlobalFinish() noexcept
 {
-	const std::lock_guard<Mutex> protect(upnp_init_mutex);
+	const std::scoped_lock<Mutex> protect(upnp_init_mutex);
 
 	assert(upnp_ref > 0);
 
diff --git a/src/mixer/MixerControl.cxx b/src/mixer/MixerControl.cxx
index 53c07bf25..8ed050482 100644
--- a/src/mixer/MixerControl.cxx
+++ b/src/mixer/MixerControl.cxx
@@ -52,7 +52,7 @@ mixer_open(Mixer *mixer)
 {
 	assert(mixer != nullptr);
 
-	const std::lock_guard<Mutex> protect(mixer->mutex);
+	const std::scoped_lock<Mutex> protect(mixer->mutex);
 
 	if (mixer->open)
 		return;
@@ -82,7 +82,7 @@ mixer_close(Mixer *mixer)
 {
 	assert(mixer != nullptr);
 
-	const std::lock_guard<Mutex> protect(mixer->mutex);
+	const std::scoped_lock<Mutex> protect(mixer->mutex);
 
 	if (mixer->open)
 		mixer_close_internal(mixer);
@@ -119,7 +119,7 @@ mixer_get_volume(Mixer *mixer)
 	if (mixer->plugin.global && !mixer->failed)
 		mixer_open(mixer);
 
-	const std::lock_guard<Mutex> protect(mixer->mutex);
+	const std::scoped_lock<Mutex> protect(mixer->mutex);
 
 	if (mixer->open) {
 		try {
@@ -143,7 +143,7 @@ mixer_set_volume(Mixer *mixer, unsigned volume)
 	if (mixer->plugin.global && !mixer->failed)
 		mixer_open(mixer);
 
-	const std::lock_guard<Mutex> protect(mixer->mutex);
+	const std::scoped_lock<Mutex> protect(mixer->mutex);
 
 	if (mixer->open)
 		mixer->SetVolume(volume);
diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
index 144059f50..87270b655 100644
--- a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
+++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
@@ -99,7 +99,7 @@ void
 SmbclientNeighborExplorer::Close() noexcept
 {
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		quit = true;
 		cond.notify_one();
 	}
@@ -110,7 +110,7 @@ SmbclientNeighborExplorer::Close() noexcept
 NeighborExplorer::List
 SmbclientNeighborExplorer::GetList() const noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return list;
 }
 
diff --git a/src/neighbor/plugins/UdisksNeighborPlugin.cxx b/src/neighbor/plugins/UdisksNeighborPlugin.cxx
index b980e6d3e..af596cdcb 100644
--- a/src/neighbor/plugins/UdisksNeighborPlugin.cxx
+++ b/src/neighbor/plugins/UdisksNeighborPlugin.cxx
@@ -175,7 +175,7 @@ UdisksNeighborExplorer::Close() noexcept
 NeighborExplorer::List
 UdisksNeighborExplorer::GetList() const noexcept
 {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 
 	NeighborExplorer::List result;
 
@@ -192,7 +192,7 @@ UdisksNeighborExplorer::Insert(UDisks2::Object &&o) noexcept
 	const NeighborInfo info = ToNeighborInfo(o);
 
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		auto i = by_uri.emplace(o.GetUri(), info);
 		if (!i.second)
 			i.first->second = info;
diff --git a/src/output/Control.cxx b/src/output/Control.cxx
index 7fc5d74b1..8b62206ee 100644
--- a/src/output/Control.cxx
+++ b/src/output/Control.cxx
@@ -79,7 +79,7 @@ AudioOutputControl::Steal() noexcept
 	StopThread();
 
 	/* now we can finally remove it */
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return std::exchange(output, nullptr);
 }
 
@@ -91,7 +91,7 @@ AudioOutputControl::ReplaceDummy(std::unique_ptr<FilteredAudioOutput> new_output
 	assert(new_output);
 
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		output = std::move(new_output);
 		enabled = _enabled;
 	}
@@ -146,7 +146,7 @@ AudioOutputControl::SetAttribute(std::string &&attribute_name,
 bool
 AudioOutputControl::LockSetEnabled(bool new_value) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (new_value == enabled)
 		return false;
@@ -158,7 +158,7 @@ AudioOutputControl::LockSetEnabled(bool new_value) noexcept
 bool
 AudioOutputControl::LockToggleEnabled() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return enabled = !enabled;
 }
 
@@ -342,14 +342,14 @@ AudioOutputControl::IsChunkConsumed(const MusicChunk &chunk) const noexcept
 bool
 AudioOutputControl::LockIsChunkConsumed(const MusicChunk &chunk) const noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return IsChunkConsumed(chunk);
 }
 
 void
 AudioOutputControl::LockPlay() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	assert(allow_play);
 
@@ -371,7 +371,7 @@ AudioOutputControl::LockPauseAsync() noexcept
 	if (output)
 		output->Interrupt();
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	assert(allow_play);
 	if (IsOpen())
@@ -381,7 +381,7 @@ AudioOutputControl::LockPauseAsync() noexcept
 void
 AudioOutputControl::LockDrainAsync() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	assert(allow_play);
 	if (IsOpen())
@@ -394,7 +394,7 @@ AudioOutputControl::LockCancelAsync() noexcept
 	if (output)
 		output->Interrupt();
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (IsOpen()) {
 		allow_play = false;
@@ -405,7 +405,7 @@ AudioOutputControl::LockCancelAsync() noexcept
 void
 AudioOutputControl::LockAllowPlay() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	allow_play = true;
 	if (IsOpen())
@@ -457,7 +457,7 @@ AudioOutputControl::BeginDestroy() noexcept
 		if (output)
 			output->Interrupt();
 
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		if (!killed) {
 			killed = true;
 			CommandAsync(Command::KILL);
diff --git a/src/output/Control.hxx b/src/output/Control.hxx
index f24c55481..fa1dfbece 100644
--- a/src/output/Control.hxx
+++ b/src/output/Control.hxx
@@ -405,7 +405,7 @@ public:
 	void EnableDisableAsync();
 
 	void LockEnableDisableAsync() {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		EnableDisableAsync();
 	}
 
@@ -480,7 +480,7 @@ public:
 	 * Locking wrapper for ClearTailChunk().
 	 */
 	void LockClearTailChunk(const MusicChunk &chunk) noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		ClearTailChunk(chunk);
 	}
 
diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx
index f7d0fbcd7..b728d06c4 100644
--- a/src/output/MultipleOutputs.cxx
+++ b/src/output/MultipleOutputs.cxx
@@ -229,7 +229,7 @@ MultipleOutputs::Open(const AudioFormat audio_format)
 	std::exception_ptr first_error;
 
 	for (const auto &ao : outputs) {
-		const std::lock_guard<Mutex> lock(ao->mutex);
+		const std::scoped_lock<Mutex> lock(ao->mutex);
 
 		if (ao->IsEnabled())
 			enabled = true;
diff --git a/src/output/State.cxx b/src/output/State.cxx
index 0e1733d44..b2648c74e 100644
--- a/src/output/State.cxx
+++ b/src/output/State.cxx
@@ -41,7 +41,7 @@ audio_output_state_save(BufferedOutputStream &os,
 {
 	for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
 		const auto &ao = outputs.Get(i);
-		const std::lock_guard<Mutex> lock(ao.mutex);
+		const std::scoped_lock<Mutex> lock(ao.mutex);
 
 		os.Format(AUDIO_DEVICE_STATE "%d:%s\n",
 			  ao.IsEnabled(), ao.GetName());
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index 846d4dc59..12f12d531 100644
--- a/src/output/plugins/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -311,13 +311,13 @@ private:
 
 	gcc_pure
 	bool LockIsActive() const noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		return active;
 	}
 
 	gcc_pure
 	bool LockIsActiveAndNotWaiting() const noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		return active && !waiting;
 	}
 
@@ -383,7 +383,7 @@ private:
 	void LockCaughtError() noexcept {
 		period_buffer.Clear();
 
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		error = std::current_exception();
 		active = false;
 		waiting = false;
@@ -398,7 +398,7 @@ private:
 	 */
 	void OnSilenceTimer() noexcept {
 		{
-			const std::lock_guard<Mutex> lock(mutex);
+			const std::scoped_lock<Mutex> lock(mutex);
 			assert(active);
 			waiting = false;
 		}
@@ -464,7 +464,7 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
 std::map<std::string, std::string>
 AlsaOutput::GetAttributes() const noexcept
 {
-	const std::lock_guard<Mutex> lock(attributes_mutex);
+	const std::scoped_lock<Mutex> lock(attributes_mutex);
 
 	return {
 		{"allowed_formats", Alsa::ToString(allowed_formats)},
@@ -478,11 +478,11 @@ void
 AlsaOutput::SetAttribute(std::string &&name, std::string &&value)
 {
 	if (name == "allowed_formats") {
-		const std::lock_guard<Mutex> lock(attributes_mutex);
+		const std::scoped_lock<Mutex> lock(attributes_mutex);
 		allowed_formats = Alsa::AllowedFormat::ParseList(value);
 #ifdef ENABLE_DSD
 	} else if (name == "dop") {
-		const std::lock_guard<Mutex> lock(attributes_mutex);
+		const std::scoped_lock<Mutex> lock(attributes_mutex);
 		if (value == "0")
 			dop_setting = false;
 		else if (value == "1")
@@ -790,7 +790,7 @@ AlsaOutput::Open(AudioFormat &audio_format)
 #endif
 
 	{
-		const std::lock_guard<Mutex> lock(attributes_mutex);
+		const std::scoped_lock<Mutex> lock(attributes_mutex);
 #ifdef ENABLE_DSD
 		dop = dop_setting;
 #endif
@@ -966,7 +966,7 @@ AlsaOutput::CopyRingToPeriodBuffer() noexcept
 
 	period_buffer.AppendBytes(nbytes);
 
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	/* notify the OutputThread that there is now
 	   room in ring_buffer */
 	cond.notify_one();
@@ -1276,7 +1276,7 @@ try {
 	}
 
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 
 		assert(active);
 
@@ -1316,7 +1316,7 @@ try {
 			   smaller than the ALSA-PCM buffer */
 
 			{
-				const std::lock_guard<Mutex> lock(mutex);
+				const std::scoped_lock<Mutex> lock(mutex);
 				waiting = true;
 				cond.notify_one();
 			}
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index add47a742..3bec12836 100644
--- a/src/output/plugins/JackOutputPlugin.cxx
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -121,7 +121,7 @@ private:
 	void Disconnect() noexcept;
 
 	void Shutdown(const char *reason) noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		error = std::make_exception_ptr(FormatRuntimeError("JACK connection shutdown: %s",
 								   reason));
 	}
@@ -185,7 +185,7 @@ public:
 
 private:
 	bool LockWasShutdown() const noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		return !!error;
 	}
 };
@@ -700,7 +700,7 @@ JackOutput::Play(const void *chunk, size_t size)
 
 	while (true) {
 		{
-			const std::lock_guard<Mutex> lock(mutex);
+			const std::scoped_lock<Mutex> lock(mutex);
 			if (error)
 				std::rethrow_exception(error);
 
@@ -730,7 +730,7 @@ inline bool
 JackOutput::Pause()
 {
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		interrupted = false;
 		if (error)
 			std::rethrow_exception(error);
diff --git a/src/output/plugins/httpd/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx
index e7e54120f..4e47b0c2d 100644
--- a/src/output/plugins/httpd/HttpdClient.cxx
+++ b/src/output/plugins/httpd/HttpdClient.cxx
@@ -47,7 +47,7 @@ HttpdClient::Close() noexcept
 void
 HttpdClient::LockClose() noexcept
 {
-	const std::lock_guard<Mutex> protect(httpd.mutex);
+	const std::scoped_lock<Mutex> protect(httpd.mutex);
 	Close();
 }
 
@@ -251,7 +251,7 @@ HttpdClient::GetBytesTillMetaData() const noexcept
 inline bool
 HttpdClient::TryWrite() noexcept
 {
-	const std::lock_guard<Mutex> protect(httpd.mutex);
+	const std::scoped_lock<Mutex> protect(httpd.mutex);
 
 	assert(state == State::RESPONSE);
 
diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx
index 1b7a606b7..178ff6df0 100644
--- a/src/output/plugins/httpd/HttpdInternal.hxx
+++ b/src/output/plugins/httpd/HttpdInternal.hxx
@@ -202,7 +202,7 @@ public:
 	 */
 	gcc_pure
 	bool LockHasClients() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return HasClients();
 	}
 
diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
index 412de7a4d..2f79e01a0 100644
--- a/src/output/plugins/httpd/HttpdOutputPlugin.cxx
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
@@ -104,7 +104,7 @@ HttpdOutput::OnDeferredBroadcast() noexcept
 	/* this method runs in the IOThread; it broadcasts pages from
 	   our own queue to all clients */
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	while (!pages.empty()) {
 		PagePtr page = std::move(pages.front());
@@ -126,7 +126,7 @@ HttpdOutput::OnAccept(UniqueSocketDescriptor fd,
 	/* the listener socket has become readable - a client has
 	   connected */
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	/* can we allow additional client */
 	if (open && (clients_max == 0 || clients.size() < clients_max))
@@ -186,7 +186,7 @@ HttpdOutput::Open(AudioFormat &audio_format)
 	assert(!open);
 	assert(clients.empty());
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	OpenEncoder(audio_format);
 
@@ -208,7 +208,7 @@ HttpdOutput::Close() noexcept
 	BlockingCall(GetEventLoop(), [this](){
 			defer_broadcast.Cancel();
 
-			const std::lock_guard<Mutex> protect(mutex);
+			const std::scoped_lock<Mutex> protect(mutex);
 			open = false;
 			clients.clear_and_dispose(DeleteDisposer());
 		});
@@ -261,7 +261,7 @@ HttpdOutput::BroadcastPage(PagePtr page) noexcept
 	assert(page != nullptr);
 
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		pages.emplace(std::move(page));
 	}
 
@@ -281,7 +281,7 @@ HttpdOutput::BroadcastFromEncoder()
 
 	PagePtr page;
 	while ((page = ReadPage()) != nullptr) {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		pages.emplace(std::move(page));
 		empty = false;
 	}
@@ -373,7 +373,7 @@ HttpdOutput::SendTag(const Tag &tag)
 
 		metadata = icy_server_metadata_page(tag, &types[0]);
 		if (metadata != nullptr) {
-			const std::lock_guard<Mutex> protect(mutex);
+			const std::scoped_lock<Mutex> protect(mutex);
 			for (auto &client : clients)
 				client.PushMetaData(metadata);
 		}
@@ -383,7 +383,7 @@ HttpdOutput::SendTag(const Tag &tag)
 inline void
 HttpdOutput::CancelAllClients() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	while (!pages.empty()) {
 		PagePtr page = std::move(pages.front());
diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx
index bcf728dcb..cdb4d4b88 100644
--- a/src/output/plugins/sles/SlesOutputPlugin.cxx
+++ b/src/output/plugins/sles/SlesOutputPlugin.cxx
@@ -398,7 +398,7 @@ SlesOutput::Cancel() noexcept
 		LogWarning(sles_domain,
 			   "AndroidSimpleBufferQueue.Clear() failed");
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	n_queued = 0;
 	filled = 0;
 }
@@ -423,7 +423,7 @@ SlesOutput::Pause()
 inline void
 SlesOutput::PlayedCallback()
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	assert(n_queued > 0);
 	--n_queued;
 	cond.notify_one();
diff --git a/src/output/plugins/snapcast/Client.cxx b/src/output/plugins/snapcast/Client.cxx
index 0c80bd95c..4b4a7601e 100644
--- a/src/output/plugins/snapcast/Client.cxx
+++ b/src/output/plugins/snapcast/Client.cxx
@@ -53,7 +53,7 @@ SnapcastClient::Close() noexcept
 void
 SnapcastClient::LockClose() noexcept
 {
-	const std::lock_guard<Mutex> protect(output.mutex);
+	const std::scoped_lock<Mutex> protect(output.mutex);
 	Close();
 }
 
@@ -70,7 +70,7 @@ SnapcastClient::Push(SnapcastChunkPtr chunk) noexcept
 SnapcastChunkPtr
 SnapcastClient::LockPopQueue() noexcept
 {
-	const std::lock_guard<Mutex> protect(output.mutex);
+	const std::scoped_lock<Mutex> protect(output.mutex);
 	if (chunks.empty())
 		return nullptr;
 
diff --git a/src/output/plugins/snapcast/Internal.hxx b/src/output/plugins/snapcast/Internal.hxx
index e21d3cd94..837b568a5 100644
--- a/src/output/plugins/snapcast/Internal.hxx
+++ b/src/output/plugins/snapcast/Internal.hxx
@@ -134,7 +134,7 @@ public:
 	 */
 	[[gnu::pure]]
 	bool LockHasClients() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return HasClients();
 	}
 
diff --git a/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx b/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx
index 1970988f7..1abe2a45a 100644
--- a/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx
+++ b/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx
@@ -114,7 +114,7 @@ SnapcastOutput::OnAccept(UniqueSocketDescriptor fd,
 	/* the listener socket has become readable - a client has
 	   connected */
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	/* can we allow additional client */
 	if (open)
@@ -152,7 +152,7 @@ SnapcastOutput::Open(AudioFormat &audio_format)
 	assert(!open);
 	assert(clients.empty());
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	OpenEncoder(audio_format);
 
@@ -174,7 +174,7 @@ SnapcastOutput::Close() noexcept
 	BlockingCall(GetEventLoop(), [this](){
 		inject_event.Cancel();
 
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		open = false;
 		clients.clear_and_dispose(DeleteDisposer{});
 	});
@@ -188,7 +188,7 @@ SnapcastOutput::Close() noexcept
 void
 SnapcastOutput::OnInject() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	while (!chunks.empty()) {
 		const auto chunk = std::move(chunks.front());
@@ -296,7 +296,7 @@ SnapcastOutput::SendTag(const Tag &tag)
 
 	const ConstBuffer payload(json.data(), json.size());
 
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	// TODO: enqueue StreamTags, don't send directly
 	for (auto &client : clients)
 		client.SendStreamTags(payload.ToVoid());
@@ -344,7 +344,7 @@ SnapcastOutput::Play(const void *chunk, size_t size)
 
 		unflushed_input = 0;
 
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		if (chunks.empty())
 			inject_event.Schedule();
 
@@ -382,7 +382,7 @@ SnapcastOutput::Drain()
 void
 SnapcastOutput::Cancel() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	ClearQueue(chunks);
 
diff --git a/src/player/Control.cxx b/src/player/Control.cxx
index 92f9c9d84..f4650bef6 100644
--- a/src/player/Control.cxx
+++ b/src/player/Control.cxx
@@ -160,7 +160,7 @@ PlayerControl::LockSetPause(bool pause_flag) noexcept
 void
 PlayerControl::LockSetBorderPause(bool _border_pause) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	border_pause = _border_pause;
 }
 
@@ -198,14 +198,14 @@ PlayerControl::SetError(PlayerError type, std::exception_ptr &&_error) noexcept
 void
 PlayerControl::LockClearError() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	ClearError();
 }
 
 void
 PlayerControl::LockSetTaggedSong(const DetachedSong &song) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	tagged_song.reset();
 	tagged_song = std::make_unique<DetachedSong>(song);
 }
@@ -225,7 +225,7 @@ PlayerControl::ReadTaggedSong() noexcept
 std::unique_ptr<DetachedSong>
 PlayerControl::LockReadTaggedSong() noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 	return ReadTaggedSong();
 }
 
diff --git a/src/player/Control.hxx b/src/player/Control.hxx
index 9c79d88a7..73f026496 100644
--- a/src/player/Control.hxx
+++ b/src/player/Control.hxx
@@ -248,7 +248,7 @@ public:
 	 * Like CheckRethrowError(), but locks and unlocks the object.
 	 */
 	void LockCheckRethrowError() const {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		CheckRethrowError();
 	}
 
@@ -317,7 +317,7 @@ public:
 	}
 
 	void LockSetReplayGainMode(ReplayGainMode _mode) noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		replay_gain_mode = _mode;
 	}
 
@@ -340,7 +340,7 @@ public:
 
 	[[gnu::pure]]
 	SyncInfo LockGetSyncInfo() const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return {state, next_song != nullptr};
 	}
 
@@ -362,7 +362,7 @@ private:
 	 * this function.
 	 */
 	void LockSignal() noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		Signal();
 	}
 
@@ -415,7 +415,7 @@ private:
 	}
 
 	void LockCommandFinished() noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		CommandFinished();
 	}
 
@@ -510,7 +510,7 @@ private:
 	}
 
 	void LockSetOutputError(std::exception_ptr &&_error) noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		SetOutputError(std::move(_error));
 	}
 
diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx
index 95ab1626e..fb53fc6f3 100644
--- a/src/player/Thread.cxx
+++ b/src/player/Thread.cxx
@@ -818,7 +818,7 @@ PlayerControl::PlayChunk(DetachedSong &song, MusicChunkPtr chunk,
 		return;
 
 	{
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		bit_rate = chunk->bit_rate;
 	}
 
@@ -942,7 +942,7 @@ Player::PlayNextChunk() noexcept
 		return false;
 	}
 
-	const std::lock_guard<Mutex> lock(pc.mutex);
+	const std::scoped_lock<Mutex> lock(pc.mutex);
 
 	/* this formula should prevent that the decoder gets woken up
 	   with each chunk; it is more efficient to make it decode a
diff --git a/src/storage/CompositeStorage.cxx b/src/storage/CompositeStorage.cxx
index 3f68cc52a..3de5f5f49 100644
--- a/src/storage/CompositeStorage.cxx
+++ b/src/storage/CompositeStorage.cxx
@@ -194,7 +194,7 @@ CompositeStorage::~CompositeStorage() = default;
 Storage *
 CompositeStorage::GetMount(std::string_view uri) noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	auto result = FindStorage(uri);
 	if (!result.uri.empty())
@@ -207,7 +207,7 @@ CompositeStorage::GetMount(std::string_view uri) noexcept
 void
 CompositeStorage::Mount(const char *uri, std::unique_ptr<Storage> storage)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	Directory &directory = root.Make(uri);
 	assert(!directory.storage);
@@ -217,7 +217,7 @@ CompositeStorage::Mount(const char *uri, std::unique_ptr<Storage> storage)
 bool
 CompositeStorage::Unmount(const char *uri)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	return root.Unmount(uri);
 }
@@ -246,7 +246,7 @@ CompositeStorage::FindStorage(std::string_view uri) const noexcept
 StorageFileInfo
 CompositeStorage::GetInfo(std::string_view uri, bool follow)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	std::exception_ptr error;
 
@@ -272,7 +272,7 @@ CompositeStorage::GetInfo(std::string_view uri, bool follow)
 std::unique_ptr<StorageDirectoryReader>
 CompositeStorage::OpenDirectory(std::string_view uri)
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	auto f = FindStorage(uri);
 	const Directory *directory = f.directory->Find(f.uri);
@@ -299,7 +299,7 @@ CompositeStorage::OpenDirectory(std::string_view uri)
 std::string
 CompositeStorage::MapUTF8(std::string_view uri) const noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	auto f = FindStorage(uri);
 	if (f.directory->storage == nullptr)
@@ -311,7 +311,7 @@ CompositeStorage::MapUTF8(std::string_view uri) const noexcept
 AllocatedPath
 CompositeStorage::MapFS(std::string_view uri) const noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	auto f = FindStorage(uri);
 	if (f.directory->storage == nullptr)
@@ -323,7 +323,7 @@ CompositeStorage::MapFS(std::string_view uri) const noexcept
 std::string_view
 CompositeStorage::MapToRelativeUTF8(std::string_view uri) const noexcept
 {
-	const std::lock_guard<Mutex> protect(mutex);
+	const std::scoped_lock<Mutex> protect(mutex);
 
 	if (root.storage != nullptr) {
 		auto result = root.storage->MapToRelativeUTF8(uri);
diff --git a/src/storage/CompositeStorage.hxx b/src/storage/CompositeStorage.hxx
index 13364ddf3..72755a43a 100644
--- a/src/storage/CompositeStorage.hxx
+++ b/src/storage/CompositeStorage.hxx
@@ -115,7 +115,7 @@ public:
 	 */
 	template<typename T>
 	void VisitMounts(T t) const {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		std::string uri;
 		VisitMounts(uri, root, t);
 	}
@@ -125,7 +125,7 @@ public:
 	 */
 	[[gnu::pure]] [[gnu::nonnull]]
 	bool IsMounted(const char *storage_uri) const noexcept {
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		return IsMounted(root, storage_uri);
 	}
 
diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx
index ef1d59853..d6f1a932d 100644
--- a/src/storage/plugins/CurlStorage.cxx
+++ b/src/storage/plugins/CurlStorage.cxx
@@ -131,7 +131,7 @@ protected:
 	}
 
 	void LockSetDone() {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		SetDone();
 	}
 
@@ -149,7 +149,7 @@ private:
 
 	/* virtual methods from CurlResponseHandler */
 	void OnError(std::exception_ptr e) noexcept final {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		postponed_error = std::move(e);
 		SetDone();
 	}
diff --git a/src/storage/plugins/NfsStorage.cxx b/src/storage/plugins/NfsStorage.cxx
index 2344495f6..9c0da37e4 100644
--- a/src/storage/plugins/NfsStorage.cxx
+++ b/src/storage/plugins/NfsStorage.cxx
@@ -139,7 +139,7 @@ private:
 	void SetState(State _state) noexcept {
 		assert(GetEventLoop().IsInside());
 
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		state = _state;
 		cond.notify_all();
 	}
@@ -147,7 +147,7 @@ private:
 	void SetState(State _state, std::exception_ptr &&e) noexcept {
 		assert(GetEventLoop().IsInside());
 
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		state = _state;
 		last_exception = std::move(e);
 		cond.notify_all();
diff --git a/src/storage/plugins/SmbclientStorage.cxx b/src/storage/plugins/SmbclientStorage.cxx
index a033f3d7f..d16e8a4b9 100644
--- a/src/storage/plugins/SmbclientStorage.cxx
+++ b/src/storage/plugins/SmbclientStorage.cxx
@@ -102,7 +102,7 @@ GetInfo(SmbclientContext &ctx, Mutex &mutex, const char *path)
 	struct stat st;
 
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		if (ctx.Stat(path, st) != 0)
 			throw MakeErrno("Failed to access file");
 	}
@@ -137,7 +137,7 @@ SmbclientStorage::OpenDirectory(std::string_view uri_utf8)
 	SMBCFILE *handle;
 
 	{
-		const std::lock_guard<Mutex> protect(mutex);
+		const std::scoped_lock<Mutex> protect(mutex);
 		handle = ctx.OpenDirectory(mapped.c_str());
 	}
 
@@ -158,14 +158,14 @@ SkipNameFS(PathTraitsFS::const_pointer name) noexcept
 
 SmbclientDirectoryReader::~SmbclientDirectoryReader()
 {
-	const std::lock_guard<Mutex> lock(storage.mutex);
+	const std::scoped_lock<Mutex> lock(storage.mutex);
 	storage.ctx.CloseDirectory(handle);
 }
 
 const char *
 SmbclientDirectoryReader::Read() noexcept
 {
-	const std::lock_guard<Mutex> protect(storage.mutex);
+	const std::scoped_lock<Mutex> protect(storage.mutex);
 
 	while (auto e = storage.ctx.ReadDirectory(handle)) {
 		name = e->name;
diff --git a/src/storage/plugins/UdisksStorage.cxx b/src/storage/plugins/UdisksStorage.cxx
index 773b1ff8a..dd30e109a 100644
--- a/src/storage/plugins/UdisksStorage.cxx
+++ b/src/storage/plugins/UdisksStorage.cxx
@@ -159,7 +159,7 @@ UdisksStorage::SetMountPoint(Path mount_point)
 void
 UdisksStorage::LockSetMountPoint(Path mount_point)
 {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	SetMountPoint(mount_point);
 }
 
@@ -191,7 +191,7 @@ UdisksStorage::OnListReply(ODBus::Message reply) noexcept
 			return;
 		}
 	} catch (...) {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		mount_error = std::current_exception();
 		want_mount = false;
 		cond.notify_all();
@@ -247,7 +247,7 @@ try {
 	mount_request.Send(connection, *msg.Get(),
 			   [this](auto o) { return OnMountNotify(std::move(o)); });
 } catch (...) {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	mount_error = std::current_exception();
 	want_mount = false;
 	cond.notify_all();
@@ -266,7 +266,7 @@ try {
 	const char *mount_path = i.GetString();
 	LockSetMountPoint(Path::FromFS(mount_path));
 } catch (...) {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	mount_error = std::current_exception();
 	want_mount = false;
 	cond.notify_all();
@@ -304,7 +304,7 @@ try {
 	mount_request.Send(connection, *msg.Get(),
 			   [this](auto u) { return OnUnmountNotify(std::move(u)); });
 } catch (...) {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	mount_error = std::current_exception();
 	mounted_storage.reset();
 	cond.notify_all();
@@ -316,12 +316,12 @@ try {
 	using namespace ODBus;
 	reply.CheckThrowError();
 
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	mount_error = {};
 	mounted_storage.reset();
 	cond.notify_all();
 } catch (...) {
-	const std::lock_guard<Mutex> lock(mutex);
+	const std::scoped_lock<Mutex> lock(mutex);
 	mount_error = std::current_exception();
 	mounted_storage.reset();
 	cond.notify_all();
diff --git a/src/tag/Builder.cxx b/src/tag/Builder.cxx
index 61bcba26f..468d3d676 100644
--- a/src/tag/Builder.cxx
+++ b/src/tag/Builder.cxx
@@ -38,7 +38,7 @@ TagBuilder::TagBuilder(const Tag &other) noexcept
 
 	const std::size_t n = other.num_items;
 	if (n > 0) {
-		const std::lock_guard<Mutex> protect(tag_pool_lock);
+		const std::scoped_lock<Mutex> protect(tag_pool_lock);
 		for (std::size_t i = 0; i != n; ++i)
 			items.push_back(tag_pool_dup_item(other.items[i]));
 	}
@@ -72,7 +72,7 @@ TagBuilder::operator=(const TagBuilder &other) noexcept
 		items = other.items;
 
 		/* increment the tag pool refcounters */
-		const std::lock_guard<Mutex> protect(tag_pool_lock);
+		const std::scoped_lock<Mutex> protect(tag_pool_lock);
 		for (auto &i : items)
 			i = tag_pool_dup_item(i);
 	}
@@ -188,7 +188,7 @@ TagBuilder::Complement(const Tag &other) noexcept
 
 	const std::size_t n = other.num_items;
 	if (n > 0) {
-		const std::lock_guard<Mutex> protect(tag_pool_lock);
+		const std::scoped_lock<Mutex> protect(tag_pool_lock);
 		for (std::size_t i = 0; i != n; ++i) {
 			TagItem *item = other.items[i];
 			if (!present[item->type])
@@ -203,7 +203,7 @@ TagBuilder::AddItemUnchecked(TagType type, StringView value) noexcept
 	TagItem *i;
 
 	{
-		const std::lock_guard<Mutex> protect(tag_pool_lock);
+		const std::scoped_lock<Mutex> protect(tag_pool_lock);
 		i = tag_pool_get_item(type, value);
 	}
 
@@ -252,7 +252,7 @@ TagBuilder::RemoveAll() noexcept
 		return;
 
 	{
-		const std::lock_guard<Mutex> protect(tag_pool_lock);
+		const std::scoped_lock<Mutex> protect(tag_pool_lock);
 		for (auto i : items)
 			tag_pool_put_item(i);
 	}
diff --git a/src/tag/Tag.cxx b/src/tag/Tag.cxx
index a9b8479f5..f5a9a7588 100644
--- a/src/tag/Tag.cxx
+++ b/src/tag/Tag.cxx
@@ -30,7 +30,7 @@ Tag::Clear() noexcept
 	has_playlist = false;
 
 	{
-		const std::lock_guard<Mutex> protect(tag_pool_lock);
+		const std::scoped_lock<Mutex> protect(tag_pool_lock);
 		for (unsigned i = 0; i < num_items; ++i)
 			tag_pool_put_item(items[i]);
 	}
@@ -47,7 +47,7 @@ Tag::Tag(const Tag &other) noexcept
 	if (num_items > 0) {
 		items = new TagItem *[num_items];
 
-		const std::lock_guard<Mutex> protect(tag_pool_lock);
+		const std::scoped_lock<Mutex> protect(tag_pool_lock);
 		for (unsigned i = 0; i < num_items; i++)
 			items[i] = tag_pool_dup_item(other.items[i]);
 	}
diff --git a/src/thread/SafeSingleton.hxx b/src/thread/SafeSingleton.hxx
index 8281d65d7..3657e5eea 100644
--- a/src/thread/SafeSingleton.hxx
+++ b/src/thread/SafeSingleton.hxx
@@ -38,7 +38,7 @@ class SafeSingleton {
 public:
 	template<typename... Args>
 	explicit SafeSingleton(Args&&... args) {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 
 		if (ref == 0)
 			instance = new T(std::forward<Args>(args)...);
@@ -50,7 +50,7 @@ public:
 	}
 
 	~SafeSingleton() noexcept {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		if (--ref > 0)
 			return;
 
diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx
index 07b2cb098..73874fe0e 100644
--- a/test/dump_text_file.cxx
+++ b/test/dump_text_file.cxx
@@ -68,7 +68,7 @@ dump_input_stream(InputStreamPtr &&is)
 		dump_text_file(tis);
 	}
 
-	const std::lock_guard<Mutex> protect(is->mutex);
+	const std::scoped_lock<Mutex> protect(is->mutex);
 
 	is->Check();
 	return 0;
diff --git a/test/run_input.cxx b/test/run_input.cxx
index 6c2f360a3..8ba9c7489 100644
--- a/test/run_input.cxx
+++ b/test/run_input.cxx
@@ -223,14 +223,14 @@ public:
 
 	/* virtual methods from RemoteTagHandler */
 	void OnRemoteTag(Tag &&_tag) noexcept override {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		tag = std::move(_tag);
 		done = true;
 		cond.notify_all();
 	}
 
 	void OnRemoteTagError(std::exception_ptr e) noexcept override {
-		const std::lock_guard<Mutex> lock(mutex);
+		const std::scoped_lock<Mutex> lock(mutex);
 		error = std::move(e);
 		done = true;
 		cond.notify_all();

From 00f8d65a179ed9474de18ef556b141384a8a6db1 Mon Sep 17 00:00:00 2001
From: Rosen Penev <rosenp@gmail.com>
Date: Thu, 11 Nov 2021 02:47:07 -0800
Subject: [PATCH 2/5] remove std::move

clang-tidy reports this is trivially copyable and thus std::move has no
effect.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 src/client/Response.cxx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/Response.cxx b/src/client/Response.cxx
index 4bfe10069..e427f30f4 100644
--- a/src/client/Response.cxx
+++ b/src/client/Response.cxx
@@ -80,7 +80,7 @@ Response::VFmtError(enum ack code,
 	Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
 	    (int)code, list_index, command);
 
-	VFmt(format_str, std::move(args));
+	VFmt(format_str, args);
 
 	Write("\n");
 }

From cfe2dd4147afad124d2fab45b4ce08b0ab4c9c30 Mon Sep 17 00:00:00 2001
From: Rosen Penev <rosenp@gmail.com>
Date: Thu, 11 Nov 2021 02:45:52 -0800
Subject: [PATCH 3/5] use nullptr

Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 src/output/plugins/PipeWireOutputPlugin.cxx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx
index 59a27c5dc..8c261bb8e 100644
--- a/src/output/plugins/PipeWireOutputPlugin.cxx
+++ b/src/output/plugins/PipeWireOutputPlugin.cxx
@@ -153,7 +153,7 @@ class PipeWireOutput final : AudioOutput {
 public:
 	static AudioOutput *Create(EventLoop &,
 				   const ConfigBlock &block) {
-		pw_init(0, nullptr);
+		pw_init(nullptr, nullptr);
 
 		return new PipeWireOutput(block);
 	}
@@ -250,7 +250,7 @@ private:
 				 uint32_t id,
 				 const struct spa_pod *param) noexcept
 	{
-		if (id != SPA_PARAM_Format || param == NULL)
+		if (id != SPA_PARAM_Format || param == nullptr)
 			return;
 
 		auto &o = *(PipeWireOutput *)data;

From 5deca66fdc5c02c1172b5df5aea2a8b731ccc5de Mon Sep 17 00:00:00 2001
From: Rosen Penev <rosenp@gmail.com>
Date: Thu, 11 Nov 2021 02:27:17 -0800
Subject: [PATCH 4/5] add various nodiscard

Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 src/archive/plugins/Iso9660ArchivePlugin.cxx | 2 +-
 src/event/ServerSocket.cxx                   | 2 +-
 src/event/SignalMonitor.cxx                  | 2 +-
 src/lib/curl/Global.cxx                      | 4 ++--
 src/mixer/plugins/AlsaMixerPlugin.cxx        | 4 ++--
 src/output/plugins/PipeWireOutputPlugin.cxx  | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/archive/plugins/Iso9660ArchivePlugin.cxx b/src/archive/plugins/Iso9660ArchivePlugin.cxx
index dc988faae..7bcfb252a 100644
--- a/src/archive/plugins/Iso9660ArchivePlugin.cxx
+++ b/src/archive/plugins/Iso9660ArchivePlugin.cxx
@@ -162,7 +162,7 @@ class Iso9660InputStream final : public InputStream {
 		std::array<uint8_t, ISO_BLOCKSIZE> data;
 
 	public:
-		ConstBuffer<uint8_t> Read() const noexcept {
+		[[nodiscard]] ConstBuffer<uint8_t> Read() const noexcept {
 			assert(fill <= data.size());
 			assert(position <= fill);
 
diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx
index 9de7241f9..a5d9abec3 100644
--- a/src/event/ServerSocket.cxx
+++ b/src/event/ServerSocket.cxx
@@ -91,7 +91,7 @@ public:
 	}
 #endif
 
-	bool IsDefined() const noexcept {
+	[[nodiscard]] bool IsDefined() const noexcept {
 		return event.IsDefined();
 	}
 
diff --git a/src/event/SignalMonitor.cxx b/src/event/SignalMonitor.cxx
index 8f9d641ee..32857081e 100644
--- a/src/event/SignalMonitor.cxx
+++ b/src/event/SignalMonitor.cxx
@@ -62,7 +62,7 @@ public:
 #endif
 	}
 
-	auto &GetEventLoop() const noexcept {
+	[[nodiscard]] auto &GetEventLoop() const noexcept {
 		return event.GetEventLoop();
 	}
 
diff --git a/src/lib/curl/Global.cxx b/src/lib/curl/Global.cxx
index 9a19e179e..218733d20 100644
--- a/src/lib/curl/Global.cxx
+++ b/src/lib/curl/Global.cxx
@@ -61,7 +61,7 @@ public:
 	CurlSocket(const CurlSocket &) = delete;
 	CurlSocket &operator=(const CurlSocket &) = delete;
 
-	auto &GetEventLoop() const noexcept {
+	[[nodiscard]] auto &GetEventLoop() const noexcept {
 		return socket_event.GetEventLoop();
 	}
 
@@ -73,7 +73,7 @@ public:
 				  void *userp, void *socketp) noexcept;
 
 private:
-	SocketDescriptor GetSocket() const noexcept {
+	[[nodiscard]] SocketDescriptor GetSocket() const noexcept {
 		return socket_event.GetSocket();
 	}
 
diff --git a/src/mixer/plugins/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx
index fa39ee120..b2d00b2ca 100644
--- a/src/mixer/plugins/AlsaMixerPlugin.cxx
+++ b/src/mixer/plugins/AlsaMixerPlugin.cxx
@@ -128,13 +128,13 @@ private:
 	}
 
 	[[gnu::pure]]
-	double GetNormalizedVolume() const noexcept {
+	[[nodiscard]] double GetNormalizedVolume() const noexcept {
 		return get_normalized_playback_volume(elem,
 						      SND_MIXER_SCHN_FRONT_LEFT);
 	}
 
 	[[gnu::pure]]
-	unsigned GetPercentVolume() const noexcept {
+	[[nodiscard]] unsigned GetPercentVolume() const noexcept {
 		return NormalizedToPercent(GetNormalizedVolume());
 	}
 
diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx
index 59a27c5dc..3dd9fe3f1 100644
--- a/src/output/plugins/PipeWireOutputPlugin.cxx
+++ b/src/output/plugins/PipeWireOutputPlugin.cxx
@@ -273,7 +273,7 @@ private:
 		pw_thread_loop_signal(thread_loop, false);
 	}
 
-	std::chrono::steady_clock::duration Delay() const noexcept override;
+	[[nodiscard]] std::chrono::steady_clock::duration Delay() const noexcept override;
 	size_t Play(const void *chunk, size_t size) override;
 
 	void Drain() override;

From 837fc98638aa723e4cfb67fa6ffe7bdd5a847672 Mon Sep 17 00:00:00 2001
From: Rosen Penev <rosenp@gmail.com>
Date: Thu, 11 Nov 2021 15:51:43 -0800
Subject: [PATCH 5/5] use const references

Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
 src/decoder/plugins/AudiofileDecoderPlugin.cxx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
index d6457f7eb..b7f1b90e9 100644
--- a/src/decoder/plugins/AudiofileDecoderPlugin.cxx
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
@@ -83,8 +83,8 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) noexcept
 static AFfileoffset
 audiofile_file_length(AFvirtualfile *vfile) noexcept
 {
-	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
-	InputStream &is = afis.is;
+	const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+	const InputStream &is = afis.is;
 
 	return is.GetSize();
 }
@@ -92,8 +92,8 @@ audiofile_file_length(AFvirtualfile *vfile) noexcept
 static AFfileoffset
 audiofile_file_tell(AFvirtualfile *vfile) noexcept
 {
-	AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
-	InputStream &is = afis.is;
+	const AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+	const InputStream &is = afis.is;
 
 	return is.GetOffset();
 }