master
cpp 130 lines 4.18 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 IOCallback.cpp
8
9 Abstract:
10
11 Holds IO callback objects.
12
13 --*/
14 #include "precomp.h"
15 #include "WslcsdkPrivate.h"
16
17 IOCallback::IOCallback(IWSLCCompatProcess* process, const WslcContainerProcessIOCallbackOptions& options) :
18 m_process(process), m_callbackOptions(std::make_unique<WslcContainerProcessIOCallbackOptions>(options))
19 {
20 using namespace wsl::windows::common::io;
21
22 auto addIOCallback = [&](WslcProcessIOHandle ioHandle, WslcStdIOCallback callback, PVOID context) {
23 std::function<void(const gsl::span<char>& Buffer)> function;
24 if (callback)
25 {
26 function = [ioHandle, callback, context](const gsl::span<char>& buffer) {
27 callback(ioHandle, reinterpret_cast<const BYTE*>(buffer.data()), static_cast<uint32_t>(buffer.size()), context);
28 };
29 }
30 else
31 {
32 function = [](const gsl::span<char>&) {};
33 }
34
35 m_io.AddHandle(std::make_unique<ReadHandle>(GetIOHandle(process, ioHandle), std::move(function)));
36 };
37
38 addIOCallback(WSLC_PROCESS_IO_HANDLE_STDOUT, options.onStdOut, options.callbackContext);
39 addIOCallback(WSLC_PROCESS_IO_HANDLE_STDERR, options.onStdErr, options.callbackContext);
40
41 if (options.onExit)
42 {
43 wil::unique_handle processExitEvent;
44 THROW_IF_FAILED(process->GetExitEvent(&processExitEvent));
45 m_io.AddHandle(std::make_unique<EventHandle>(std::move(processExitEvent)));
46 }
47
48 m_io.AddHandle(std::make_unique<EventHandle>(m_cancelEvent.get()), MultiHandleWait::CancelOnCompleted | MultiHandleWait::NeedNotComplete);
49
50 m_thread = std::thread([this]() {
51 try
52 {
53 // Will be false when cancelled.
54 bool runResult = m_io.Run({});
55
56 if (runResult && m_process && m_callbackOptions && m_callbackOptions->onExit)
57 {
58 WSLCProcessState state{};
59 int exitCode = -1;
60
61 // Prefer to make the callback even if we don't properly retrieve the exit code.
62 if (FAILED_LOG(m_process->GetState(&state, &exitCode)))
63 {
64 // Reset to our known value in case GetState stomped it while failing.
65 exitCode = -1;
66 }
67 else
68 {
69 WI_ASSERT(state == WslcProcessStateExited);
70 }
71
72 // Regardless of our ability to get the proper exit code, inform the caller that the process
73 // has exited and they will not be getting any additional IO callbacks.
74 m_callbackOptions->onExit(exitCode, m_callbackOptions->callbackContext);
75 }
76 }
77 CATCH_LOG();
78 });
79 }
80
81 IOCallback::~IOCallback()
82 try
83 {
84 Cancel();
85 Complete();
86 }
87 CATCH_LOG();
88
89 void IOCallback::Cancel()
90 {
91 m_cancelEvent.SetEvent();
92 }
93
94 void IOCallback::Complete()
95 {
96 // Complete can be called by multiple threads. Make sure that it's only ever called once since join() is not thread safe.
97 std::call_once(m_join, [this]() {
98 if (m_thread.joinable())
99 {
100 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE_STATE), IsOnIOCallbackThread());
101
102 m_thread.join();
103 }
104 });
105 }
106
107 bool IOCallback::IsOnIOCallbackThread() const noexcept
108 {
109 return m_thread.get_id() == std::this_thread::get_id();
110 }
111
112 bool IOCallback::HasIOCallback(const WslcContainerProcessOptionsInternal* options)
113 {
114 return options && HasIOCallback(options->ioCallbacks);
115 }
116
117 bool IOCallback::HasIOCallback(const WslcContainerProcessIOCallbackOptions& options)
118 {
119 return options.onStdOut || options.onStdErr || options.onExit;
120 }
121
122 wil::unique_handle IOCallback::GetIOHandle(IWSLCCompatProcess* process, WslcProcessIOHandle ioHandle)
123 {
124 WSLCCompatHandle handle{};
125
126 THROW_IF_FAILED(process->GetStdHandle(static_cast<WSLCFD>(static_cast<std::underlying_type_t<WslcProcessIOHandle>>(ioHandle)), &handle));
127
128 // The handle value is the same regardless of the union member that was populated.
129 return wil::unique_handle{handle.Handle.File};
130 }