summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/cmUVHandlePtr.cxx37
-rw-r--r--Source/cmUVHandlePtr.h24
-rw-r--r--Tests/CMakeLib/testUVHandlePtr.cxx51
3 files changed, 112 insertions, 0 deletions
diff --git a/Source/cmUVHandlePtr.cxx b/Source/cmUVHandlePtr.cxx
index 7d41644..168f1a6 100644
--- a/Source/cmUVHandlePtr.cxx
+++ b/Source/cmUVHandlePtr.cxx
@@ -6,6 +6,9 @@
#include <cassert>
#include <cstdlib>
#include <mutex>
+#include <utility>
+
+#include <cm/memory>
#include <cm3p/uv.h>
@@ -306,4 +309,38 @@ UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async)
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(tty)
#endif
+
+namespace {
+struct write_req : public uv_write_t
+{
+ std::weak_ptr<std::function<void(int)>> cb_;
+ write_req(std::weak_ptr<std::function<void(int)>> wcb)
+ : cb_(std::move(wcb))
+ {
+ }
+};
+
+void write_req_cb(uv_write_t* req, int status)
+{
+ // Ownership has been transferred from the event loop.
+ std::unique_ptr<write_req> self(static_cast<write_req*>(req));
+
+ // Notify the original uv_write caller if it is still interested.
+ if (auto cb = self->cb_.lock()) {
+ (*cb)(status);
+ }
+}
+}
+
+int uv_write(uv_stream_t* handle, const uv_buf_t bufs[], unsigned int nbufs,
+ std::weak_ptr<std::function<void(int)>> cb)
+{
+ auto req = cm::make_unique<write_req>(std::move(cb));
+ int status = uv_write(req.get(), handle, bufs, nbufs, write_req_cb);
+ if (status == 0) {
+ // Ownership has been transferred to the event loop.
+ static_cast<void>(req.release());
+ }
+ return status;
+}
}
diff --git a/Source/cmUVHandlePtr.h b/Source/cmUVHandlePtr.h
index 174dc45..1b5eb9c 100644
--- a/Source/cmUVHandlePtr.h
+++ b/Source/cmUVHandlePtr.h
@@ -5,6 +5,7 @@
#include <cstddef>
#include <cstdint>
+#include <functional>
#include <memory>
#include <type_traits>
@@ -286,4 +287,27 @@ UV_HANDLE_PTR_INSTANTIATE_EXTERN(tty)
# undef UV_HANDLE_PTR_INSTANTIATE_EXTERN
#endif
+
+/**
+ * Wraps uv_write to add synchronous cancellation.
+ *
+ * libuv provides no way to synchronously cancel a write request.
+ * Closing a write handle will cancel its pending write request, but its
+ * callback will still be called asynchronously later with UV_ECANCELED.
+ *
+ * This wrapper provides a solution by handing ownership of the uv_write_t
+ * request object to the event loop and taking it back in the callback.
+ * Use this in combination with uv_loop_ptr to ensure the event loop
+ * runs to completion and cleans up all resources.
+ *
+ * The caller may optionally provide a callback it owns with std::shared_ptr.
+ * If the caller's lifetime ends before the write request completes, the
+ * callback can be safely deleted and will not be called.
+ *
+ * The bufs array does not need to live beyond this call, but the memory
+ * referenced by the uv_buf_t values must remain alive until the callback
+ * is made or the stream is closed.
+ */
+int uv_write(uv_stream_t* handle, const uv_buf_t bufs[], unsigned int nbufs,
+ std::weak_ptr<std::function<void(int)>> cb);
}
diff --git a/Tests/CMakeLib/testUVHandlePtr.cxx b/Tests/CMakeLib/testUVHandlePtr.cxx
index 3c070b0..c97d755 100644
--- a/Tests/CMakeLib/testUVHandlePtr.cxx
+++ b/Tests/CMakeLib/testUVHandlePtr.cxx
@@ -1,7 +1,10 @@
+#include <functional>
#include <iostream>
+#include <memory>
#include <cm3p/uv.h>
+#include "cmGetPipes.h"
#include "cmUVHandlePtr.h"
static bool testIdle()
@@ -77,10 +80,58 @@ static bool testTimer()
return true;
}
+static bool testWriteCallback()
+{
+ int pipe[] = { -1, -1 };
+ if (cmGetPipes(pipe) < 0) {
+ std::cout << "cmGetPipes() returned an error" << std::endl;
+ return false;
+ }
+
+ cm::uv_loop_ptr loop;
+ loop.init();
+
+ cm::uv_pipe_ptr pipeRead;
+ pipeRead.init(*loop, 0);
+ uv_pipe_open(pipeRead, pipe[0]);
+
+ cm::uv_pipe_ptr pipeWrite;
+ pipeWrite.init(*loop, 0);
+ uv_pipe_open(pipeWrite, pipe[1]);
+
+ char c = '.';
+ uv_buf_t buf = uv_buf_init(&c, sizeof(c));
+ int status = -1;
+ auto cb = std::make_shared<std::function<void(int)>>(
+ [&status](int s) { status = s; });
+
+ // Test getting a callback after the write is done.
+ cm::uv_write(pipeWrite, &buf, 1, cb);
+ uv_run(loop, UV_RUN_DEFAULT);
+ if (status != 0) {
+ std::cout << "cm::uv_write non-zero status: " << status << std::endl;
+ return false;
+ }
+
+ // Test deleting the callback before it is made.
+ status = -1;
+ cm::uv_write(pipeWrite, &buf, 1, cb);
+ cb.reset();
+ uv_run(loop, UV_RUN_DEFAULT);
+ if (status != -1) {
+ std::cout << "cm::uv_write callback incorrectly called with status: "
+ << status << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
int testUVHandlePtr(int, char** const)
{
bool passed = true;
passed = testIdle() && passed;
passed = testTimer() && passed;
+ passed = testWriteCallback() && passed;
return passed ? 0 : -1;
}