diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1f5054e877d7a2bff25850b8b413ba177da73ed4..8914b7c912017e73d788d75cb6320a35aab3f4e7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "flowdalic/debian-testing-dev:1.17"
+image: "flowdalic/debian-testing-dev:1.19"
 
 before_script:
   - ulimit -a
@@ -97,6 +97,14 @@ clang-tidy:
     CC: clang
     CXX: clang++
 
+.libc++:
+  extends:
+    - .clang
+  variables:
+    EMPER_USE_BUNDLED_DEPS: "always"
+    EMPER_CPP_ARGS: "-stdlib=libc++"
+    EMPER_CPP_LINK_ARGS: "-stdlib=libc++"
+
 .emper-ws-scheduling:
   variables:
     EMPER_DEFAULT_SCHEDULING_STRATEGY: "work_stealing"
@@ -253,6 +261,17 @@ test-clang-debug:
     - test-clang
     - .debug-build
 
+smoke-test-libc++:
+  stage: smoke-test
+  extends:
+    - .fast-variant-check
+    - .libc++
+
+test-libc++:
+  extends:
+    - .test
+    - .libc++
+
 test-worker-no-sleep:
   extends:
     - .test
diff --git a/Makefile b/Makefile
index 07ee51e9b5f7834e6fef7ee15713a6307ee1e187..f96f4cdb8151107419fa8c2e8e71f1cb6a48a4c5 100644
--- a/Makefile
+++ b/Makefile
@@ -50,6 +50,15 @@ PHONY: sanitize-address
 sanitize-address:
 	$(sanitizer)
 
+libc++:
+	rm -f build
+	$(MAKE) build \
+		CC=clang CXX=clang++ \
+		EMPER_CPP_ARGS="-stdlib=libc++" \
+		EMPER_CPP_LINK_ARGS="-stdlib=libc++" \
+		EMPER_USE_BUNDLED_DEPS="always" \
+		BUILDDIR="build-libc++"
+
 .PHONY: fast-static-analysis
 fast-static-analysis: all check-format check-license doc
 
diff --git a/emper/log/log.cpp b/emper/log/log.cpp
index 14140f462bd3e46906a0a3ca132d8cc0d2a065b9..b975f1d9cd8b61d9331e512fd9b168d3516a214e 100644
--- a/emper/log/log.cpp
+++ b/emper/log/log.cpp
@@ -21,15 +21,13 @@
 
 using emper::io::GlobalIoContext;
 
-//static const long NanosInAMinute = 60L * 1000 * 1000 * 1000;
-
 namespace emper::log {
 
-static void add_timestamp_to(UNUSED_ARG std::ostringstream& logMessage) {
-#if 0
-	auto now = std::chrono::high_resolution_clock::now();
+static void add_timestamp_to(std::ostringstream& logMessage) {
+	using clock = std::chrono::system_clock;
 
-	auto now_time_t = std::chrono::high_resolution_clock::to_time_t(now);
+	auto now = clock::now();
+	auto now_time_t = clock::to_time_t(now);
 
 	const std::tm* now_localtime = [&now_time_t] {
 		if constexpr (emper::LOG_TIMESTAMP == emper::LogTimeStamp::utc) {
@@ -52,9 +50,9 @@ static void add_timestamp_to(UNUSED_ARG std::ostringstream& logMessage) {
 	auto time_since_epoch = now_nanos.time_since_epoch();
 	long time_since_epoch_long = time_since_epoch.count();
 
+	const long NanosInAMinute = 60L * 1000 * 1000 * 1000;
 	long remaining_nanos = time_since_epoch_long % NanosInAMinute;
 	logMessage << remaining_nanos;
-#endif
 }
 
 static std::mutex log_mutex;
diff --git a/meson.build b/meson.build
index d1c278b97d5a69a03446374166869cf2d419a1ab..80b1df3f9eca4bb3c2a3f4836e65596350002cc6 100644
--- a/meson.build
+++ b/meson.build
@@ -10,9 +10,12 @@ project('EMPER', 'c', 'cpp',
 
 thread_dep = dependency('threads')
 
+conf_data = configuration_data()
+use_bundled_deps = get_option('use_bundled_deps')
+
 liburing_version = '2.0'
-uring_dep = dependency('liburing', version: liburing_version, required: false)
-if not uring_dep.found()
+uring_dep = dependency('liburing', version: liburing_version, required: use_bundled_deps == 'never')
+if not uring_dep.found() or use_bundled_deps == 'always'
 	message('liburing ' + liburing_version + ' not found in system, using liburing subproject')
 	liburing_sp = subproject('liburing')
 	uring_dep = liburing_sp.get_variable('uring').as_system()
@@ -32,7 +35,6 @@ tools_dir = join_paths(meson.source_root(), 'tools')
 run_target('iwyu',
 		   command: join_paths(tools_dir, 'check-iwyu'))
 
-conf_data = configuration_data()
 option_urcu = get_option('userspace_rcu')
 conf_data.set('EMPER_LIBURCU', option_urcu)
 if option_urcu
diff --git a/meson_options.txt b/meson_options.txt
index 3cced4d588766dbbefb40d9c2b94e7d68bd81f44..96b76c34be3435f7737a42e7b4523b7811d2f014 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,3 +1,10 @@
+option(
+  'use_bundled_deps',
+  type: 'combo',
+  choices: ['automatic', 'always', 'never'],
+  description: 'Control when the bundled dependencies are used',
+  value: 'automatic',
+)
 option(
   'userspace_rcu',
   type: 'boolean',
diff --git a/tests/io/TimeoutTest.cpp b/tests/io/TimeoutTest.cpp
index a46813c5cfcdb767dc6b81ae3b9a9dbc5ddaeb45..137e17ab6c56267e57019447acba654fb0700c00 100644
--- a/tests/io/TimeoutTest.cpp
+++ b/tests/io/TimeoutTest.cpp
@@ -7,6 +7,7 @@
 #include <cerrno>
 #include <cstdint>
 #include <cstring>
+#include <memory>
 
 #include "Common.hpp"
 #include "CountingPrivateSemaphore.hpp"
@@ -15,6 +16,7 @@
 #include "emper.hpp"
 #include "fixtures/assert.hpp"
 #include "io.hpp"
+#include "lib/LinuxVersion.hpp"
 
 using emper::io::ReadFuture;
 using emper::io::TimeoutWrapper;
@@ -122,7 +124,8 @@ void writeTest() {
 	ASSERT(res == -1);
 	// write requests can't be canceled when in execution so this
 	// will return as interupted
-	ASSERT(errno == EINTR);
+	const int err = errno;
+	ASSERT(err == EINTR || (EMPER_LINUX_GE("5.16.0") && err == ECANCELED));
 
 	emper::io::closeAndForget(efd);
 }
diff --git a/tests/meson.build b/tests/meson.build
index 8204a0b7602990d0d968049febcfdb84f7ebaff4..c1d0d09a2aee9f1cb30a7367dad830a892b685fb 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -133,10 +133,10 @@ parallel_ok = (not io_uring_enabled)
 
 # Meson integration for GTest and GMock
 # See https://mesonbuild.com/Dependencies.html#gtest-and-gmock
-gtest_dep = dependency('gtest', main: true, required: false)
+gtest_dep = dependency('gtest', main: true, required: use_bundled_deps == 'never')
 # If gtest is not available on the system use the meson wrap provided
 # by WrapDB and build it ourself
-if not gtest_dep.found()
+if not gtest_dep.found() or use_bundled_deps == 'always'
 	gtest_sp = subproject('gtest')
 	gtest_dep = gtest_sp.get_variable('gtest_main_dep')
 endif