1dda28197Spatrick //===-- GDBRemoteClientBase.cpp -------------------------------------------===//
2061da546Spatrick //
3061da546Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4061da546Spatrick // See https://llvm.org/LICENSE.txt for license information.
5061da546Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6061da546Spatrick //
7061da546Spatrick //===----------------------------------------------------------------------===//
8061da546Spatrick
9061da546Spatrick #include "GDBRemoteClientBase.h"
10061da546Spatrick
11061da546Spatrick #include "llvm/ADT/StringExtras.h"
12061da546Spatrick
13061da546Spatrick #include "lldb/Target/UnixSignals.h"
14061da546Spatrick #include "lldb/Utility/LLDBAssert.h"
15061da546Spatrick
16061da546Spatrick #include "ProcessGDBRemoteLog.h"
17061da546Spatrick
18061da546Spatrick using namespace lldb;
19061da546Spatrick using namespace lldb_private;
20061da546Spatrick using namespace lldb_private::process_gdb_remote;
21061da546Spatrick using namespace std::chrono;
22061da546Spatrick
23be691f3bSpatrick // When we've sent a continue packet and are waiting for the target to stop,
24be691f3bSpatrick // we wake up the wait with this interval to make sure the stub hasn't gone
25be691f3bSpatrick // away while we were waiting.
26be691f3bSpatrick static const seconds kWakeupInterval(5);
27061da546Spatrick
28061da546Spatrick /////////////////////////
29061da546Spatrick // GDBRemoteClientBase //
30061da546Spatrick /////////////////////////
31061da546Spatrick
32061da546Spatrick GDBRemoteClientBase::ContinueDelegate::~ContinueDelegate() = default;
33061da546Spatrick
GDBRemoteClientBase(const char * comm_name)34*f6aab3d8Srobert GDBRemoteClientBase::GDBRemoteClientBase(const char *comm_name)
35*f6aab3d8Srobert : GDBRemoteCommunication(), Broadcaster(nullptr, comm_name),
36*f6aab3d8Srobert m_async_count(0), m_is_running(false), m_should_stop(false) {}
37061da546Spatrick
SendContinuePacketAndWaitForResponse(ContinueDelegate & delegate,const UnixSignals & signals,llvm::StringRef payload,std::chrono::seconds interrupt_timeout,StringExtractorGDBRemote & response)38061da546Spatrick StateType GDBRemoteClientBase::SendContinuePacketAndWaitForResponse(
39061da546Spatrick ContinueDelegate &delegate, const UnixSignals &signals,
40be691f3bSpatrick llvm::StringRef payload, std::chrono::seconds interrupt_timeout,
41be691f3bSpatrick StringExtractorGDBRemote &response) {
42*f6aab3d8Srobert Log *log = GetLog(GDBRLog::Process);
43061da546Spatrick response.Clear();
44061da546Spatrick
45061da546Spatrick {
46061da546Spatrick std::lock_guard<std::mutex> lock(m_mutex);
47dda28197Spatrick m_continue_packet = std::string(payload);
48061da546Spatrick m_should_stop = false;
49061da546Spatrick }
50061da546Spatrick ContinueLock cont_lock(*this);
51061da546Spatrick if (!cont_lock)
52061da546Spatrick return eStateInvalid;
53061da546Spatrick OnRunPacketSent(true);
54be691f3bSpatrick // The main ReadPacket loop wakes up at computed_timeout intervals, just to
55be691f3bSpatrick // check that the connection hasn't dropped. When we wake up we also check
56be691f3bSpatrick // whether there is an interrupt request that has reached its endpoint.
57be691f3bSpatrick // If we want a shorter interrupt timeout that kWakeupInterval, we need to
58be691f3bSpatrick // choose the shorter interval for the wake up as well.
59be691f3bSpatrick std::chrono::seconds computed_timeout = std::min(interrupt_timeout,
60be691f3bSpatrick kWakeupInterval);
61061da546Spatrick for (;;) {
62be691f3bSpatrick PacketResult read_result = ReadPacket(response, computed_timeout, false);
63be691f3bSpatrick // Reset the computed_timeout to the default value in case we are going
64be691f3bSpatrick // round again.
65be691f3bSpatrick computed_timeout = std::min(interrupt_timeout, kWakeupInterval);
66061da546Spatrick switch (read_result) {
67061da546Spatrick case PacketResult::ErrorReplyTimeout: {
68061da546Spatrick std::lock_guard<std::mutex> lock(m_mutex);
69be691f3bSpatrick if (m_async_count == 0) {
70061da546Spatrick continue;
71be691f3bSpatrick }
72be691f3bSpatrick auto cur_time = steady_clock::now();
73be691f3bSpatrick if (cur_time >= m_interrupt_endpoint)
74061da546Spatrick return eStateInvalid;
75be691f3bSpatrick else {
76be691f3bSpatrick // We woke up and found an interrupt is in flight, but we haven't
77be691f3bSpatrick // exceeded the interrupt wait time. So reset the wait time to the
78be691f3bSpatrick // time left till the interrupt timeout. But don't wait longer
79be691f3bSpatrick // than our wakeup timeout.
80be691f3bSpatrick auto new_wait = m_interrupt_endpoint - cur_time;
81be691f3bSpatrick computed_timeout = std::min(kWakeupInterval,
82be691f3bSpatrick std::chrono::duration_cast<std::chrono::seconds>(new_wait));
83be691f3bSpatrick continue;
84be691f3bSpatrick }
85061da546Spatrick break;
86061da546Spatrick }
87061da546Spatrick case PacketResult::Success:
88061da546Spatrick break;
89061da546Spatrick default:
90061da546Spatrick LLDB_LOGF(log, "GDBRemoteClientBase::%s () ReadPacket(...) => false",
91061da546Spatrick __FUNCTION__);
92061da546Spatrick return eStateInvalid;
93061da546Spatrick }
94061da546Spatrick if (response.Empty())
95061da546Spatrick return eStateInvalid;
96061da546Spatrick
97061da546Spatrick const char stop_type = response.GetChar();
98061da546Spatrick LLDB_LOGF(log, "GDBRemoteClientBase::%s () got packet: %s", __FUNCTION__,
99061da546Spatrick response.GetStringRef().data());
100061da546Spatrick
101061da546Spatrick switch (stop_type) {
102061da546Spatrick case 'W':
103061da546Spatrick case 'X':
104061da546Spatrick return eStateExited;
105061da546Spatrick case 'E':
106061da546Spatrick // ERROR
107061da546Spatrick return eStateInvalid;
108061da546Spatrick default:
109061da546Spatrick LLDB_LOGF(log, "GDBRemoteClientBase::%s () unrecognized async packet",
110061da546Spatrick __FUNCTION__);
111061da546Spatrick return eStateInvalid;
112061da546Spatrick case 'O': {
113061da546Spatrick std::string inferior_stdout;
114061da546Spatrick response.GetHexByteString(inferior_stdout);
115061da546Spatrick delegate.HandleAsyncStdout(inferior_stdout);
116061da546Spatrick break;
117061da546Spatrick }
118061da546Spatrick case 'A':
119061da546Spatrick delegate.HandleAsyncMisc(
120061da546Spatrick llvm::StringRef(response.GetStringRef()).substr(1));
121061da546Spatrick break;
122061da546Spatrick case 'J':
123061da546Spatrick delegate.HandleAsyncStructuredDataPacket(response.GetStringRef());
124061da546Spatrick break;
125061da546Spatrick case 'T':
126061da546Spatrick case 'S':
127061da546Spatrick // Do this with the continue lock held.
128061da546Spatrick const bool should_stop = ShouldStop(signals, response);
129061da546Spatrick response.SetFilePos(0);
130061da546Spatrick
131061da546Spatrick // The packet we should resume with. In the future we should check our
132061da546Spatrick // thread list and "do the right thing" for new threads that show up
133061da546Spatrick // while we stop and run async packets. Setting the packet to 'c' to
134061da546Spatrick // continue all threads is the right thing to do 99.99% of the time
135061da546Spatrick // because if a thread was single stepping, and we sent an interrupt, we
136061da546Spatrick // will notice above that we didn't stop due to an interrupt but stopped
137061da546Spatrick // due to stepping and we would _not_ continue. This packet may get
138061da546Spatrick // modified by the async actions (e.g. to send a signal).
139061da546Spatrick m_continue_packet = 'c';
140061da546Spatrick cont_lock.unlock();
141061da546Spatrick
142061da546Spatrick delegate.HandleStopReply();
143061da546Spatrick if (should_stop)
144061da546Spatrick return eStateStopped;
145061da546Spatrick
146061da546Spatrick switch (cont_lock.lock()) {
147061da546Spatrick case ContinueLock::LockResult::Success:
148061da546Spatrick break;
149061da546Spatrick case ContinueLock::LockResult::Failed:
150061da546Spatrick return eStateInvalid;
151061da546Spatrick case ContinueLock::LockResult::Cancelled:
152061da546Spatrick return eStateStopped;
153061da546Spatrick }
154061da546Spatrick OnRunPacketSent(false);
155061da546Spatrick break;
156061da546Spatrick }
157061da546Spatrick }
158061da546Spatrick }
159061da546Spatrick
SendAsyncSignal(int signo,std::chrono::seconds interrupt_timeout)160be691f3bSpatrick bool GDBRemoteClientBase::SendAsyncSignal(
161be691f3bSpatrick int signo, std::chrono::seconds interrupt_timeout) {
162be691f3bSpatrick Lock lock(*this, interrupt_timeout);
163061da546Spatrick if (!lock || !lock.DidInterrupt())
164061da546Spatrick return false;
165061da546Spatrick
166061da546Spatrick m_continue_packet = 'C';
167061da546Spatrick m_continue_packet += llvm::hexdigit((signo / 16) % 16);
168061da546Spatrick m_continue_packet += llvm::hexdigit(signo % 16);
169061da546Spatrick return true;
170061da546Spatrick }
171061da546Spatrick
Interrupt(std::chrono::seconds interrupt_timeout)172be691f3bSpatrick bool GDBRemoteClientBase::Interrupt(std::chrono::seconds interrupt_timeout) {
173be691f3bSpatrick Lock lock(*this, interrupt_timeout);
174061da546Spatrick if (!lock.DidInterrupt())
175061da546Spatrick return false;
176061da546Spatrick m_should_stop = true;
177061da546Spatrick return true;
178061da546Spatrick }
179be691f3bSpatrick
180061da546Spatrick GDBRemoteCommunication::PacketResult
SendPacketAndWaitForResponse(llvm::StringRef payload,StringExtractorGDBRemote & response,std::chrono::seconds interrupt_timeout)181061da546Spatrick GDBRemoteClientBase::SendPacketAndWaitForResponse(
182061da546Spatrick llvm::StringRef payload, StringExtractorGDBRemote &response,
183be691f3bSpatrick std::chrono::seconds interrupt_timeout) {
184be691f3bSpatrick Lock lock(*this, interrupt_timeout);
185061da546Spatrick if (!lock) {
186*f6aab3d8Srobert if (Log *log = GetLog(GDBRLog::Process))
187061da546Spatrick LLDB_LOGF(log,
188061da546Spatrick "GDBRemoteClientBase::%s failed to get mutex, not sending "
189be691f3bSpatrick "packet '%.*s'",
190be691f3bSpatrick __FUNCTION__, int(payload.size()), payload.data());
191061da546Spatrick return PacketResult::ErrorSendFailed;
192061da546Spatrick }
193061da546Spatrick
194061da546Spatrick return SendPacketAndWaitForResponseNoLock(payload, response);
195061da546Spatrick }
196061da546Spatrick
197061da546Spatrick GDBRemoteCommunication::PacketResult
ReadPacketWithOutputSupport(StringExtractorGDBRemote & response,Timeout<std::micro> timeout,bool sync_on_timeout,llvm::function_ref<void (llvm::StringRef)> output_callback)198*f6aab3d8Srobert GDBRemoteClientBase::ReadPacketWithOutputSupport(
199*f6aab3d8Srobert StringExtractorGDBRemote &response, Timeout<std::micro> timeout,
200*f6aab3d8Srobert bool sync_on_timeout,
201*f6aab3d8Srobert llvm::function_ref<void(llvm::StringRef)> output_callback) {
202*f6aab3d8Srobert auto result = ReadPacket(response, timeout, sync_on_timeout);
203*f6aab3d8Srobert while (result == PacketResult::Success && response.IsNormalResponse() &&
204*f6aab3d8Srobert response.PeekChar() == 'O') {
205*f6aab3d8Srobert response.GetChar();
206*f6aab3d8Srobert std::string output;
207*f6aab3d8Srobert if (response.GetHexByteString(output))
208*f6aab3d8Srobert output_callback(output);
209*f6aab3d8Srobert result = ReadPacket(response, timeout, sync_on_timeout);
210*f6aab3d8Srobert }
211*f6aab3d8Srobert return result;
212*f6aab3d8Srobert }
213*f6aab3d8Srobert
214*f6aab3d8Srobert GDBRemoteCommunication::PacketResult
SendPacketAndReceiveResponseWithOutputSupport(llvm::StringRef payload,StringExtractorGDBRemote & response,std::chrono::seconds interrupt_timeout,llvm::function_ref<void (llvm::StringRef)> output_callback)215061da546Spatrick GDBRemoteClientBase::SendPacketAndReceiveResponseWithOutputSupport(
216061da546Spatrick llvm::StringRef payload, StringExtractorGDBRemote &response,
217be691f3bSpatrick std::chrono::seconds interrupt_timeout,
218061da546Spatrick llvm::function_ref<void(llvm::StringRef)> output_callback) {
219be691f3bSpatrick Lock lock(*this, interrupt_timeout);
220061da546Spatrick if (!lock) {
221*f6aab3d8Srobert if (Log *log = GetLog(GDBRLog::Process))
222061da546Spatrick LLDB_LOGF(log,
223061da546Spatrick "GDBRemoteClientBase::%s failed to get mutex, not sending "
224be691f3bSpatrick "packet '%.*s'",
225be691f3bSpatrick __FUNCTION__, int(payload.size()), payload.data());
226061da546Spatrick return PacketResult::ErrorSendFailed;
227061da546Spatrick }
228061da546Spatrick
229061da546Spatrick PacketResult packet_result = SendPacketNoLock(payload);
230061da546Spatrick if (packet_result != PacketResult::Success)
231061da546Spatrick return packet_result;
232061da546Spatrick
233061da546Spatrick return ReadPacketWithOutputSupport(response, GetPacketTimeout(), true,
234061da546Spatrick output_callback);
235061da546Spatrick }
236061da546Spatrick
237061da546Spatrick GDBRemoteCommunication::PacketResult
SendPacketAndWaitForResponseNoLock(llvm::StringRef payload,StringExtractorGDBRemote & response)238061da546Spatrick GDBRemoteClientBase::SendPacketAndWaitForResponseNoLock(
239061da546Spatrick llvm::StringRef payload, StringExtractorGDBRemote &response) {
240061da546Spatrick PacketResult packet_result = SendPacketNoLock(payload);
241061da546Spatrick if (packet_result != PacketResult::Success)
242061da546Spatrick return packet_result;
243061da546Spatrick
244061da546Spatrick const size_t max_response_retries = 3;
245061da546Spatrick for (size_t i = 0; i < max_response_retries; ++i) {
246061da546Spatrick packet_result = ReadPacket(response, GetPacketTimeout(), true);
247061da546Spatrick // Make sure we received a response
248061da546Spatrick if (packet_result != PacketResult::Success)
249061da546Spatrick return packet_result;
250061da546Spatrick // Make sure our response is valid for the payload that was sent
251061da546Spatrick if (response.ValidateResponse())
252061da546Spatrick return packet_result;
253061da546Spatrick // Response says it wasn't valid
254*f6aab3d8Srobert Log *log = GetLog(GDBRLog::Packets);
255061da546Spatrick LLDB_LOGF(
256061da546Spatrick log,
257061da546Spatrick "error: packet with payload \"%.*s\" got invalid response \"%s\": %s",
258061da546Spatrick int(payload.size()), payload.data(), response.GetStringRef().data(),
259061da546Spatrick (i == (max_response_retries - 1))
260061da546Spatrick ? "using invalid response and giving up"
261061da546Spatrick : "ignoring response and waiting for another");
262061da546Spatrick }
263061da546Spatrick return packet_result;
264061da546Spatrick }
265061da546Spatrick
ShouldStop(const UnixSignals & signals,StringExtractorGDBRemote & response)266061da546Spatrick bool GDBRemoteClientBase::ShouldStop(const UnixSignals &signals,
267061da546Spatrick StringExtractorGDBRemote &response) {
268061da546Spatrick std::lock_guard<std::mutex> lock(m_mutex);
269061da546Spatrick
270061da546Spatrick if (m_async_count == 0)
271061da546Spatrick return true; // We were not interrupted. The process stopped on its own.
272061da546Spatrick
273061da546Spatrick // Older debugserver stubs (before April 2016) can return two stop-reply
274061da546Spatrick // packets in response to a ^C packet. Additionally, all debugservers still
275061da546Spatrick // return two stop replies if the inferior stops due to some other reason
276061da546Spatrick // before the remote stub manages to interrupt it. We need to wait for this
277061da546Spatrick // additional packet to make sure the packet sequence does not get skewed.
278061da546Spatrick StringExtractorGDBRemote extra_stop_reply_packet;
279061da546Spatrick ReadPacket(extra_stop_reply_packet, milliseconds(100), false);
280061da546Spatrick
281061da546Spatrick // Interrupting is typically done using SIGSTOP or SIGINT, so if the process
282061da546Spatrick // stops with some other signal, we definitely want to stop.
283061da546Spatrick const uint8_t signo = response.GetHexU8(UINT8_MAX);
284061da546Spatrick if (signo != signals.GetSignalNumberFromName("SIGSTOP") &&
285061da546Spatrick signo != signals.GetSignalNumberFromName("SIGINT"))
286061da546Spatrick return true;
287061da546Spatrick
288061da546Spatrick // We probably only stopped to perform some async processing, so continue
289061da546Spatrick // after that is done.
290061da546Spatrick // TODO: This is not 100% correct, as the process may have been stopped with
291061da546Spatrick // SIGINT or SIGSTOP that was not caused by us (e.g. raise(SIGINT)). This will
292061da546Spatrick // normally cause a stop, but if it's done concurrently with a async
293061da546Spatrick // interrupt, that stop will get eaten (llvm.org/pr20231).
294061da546Spatrick return false;
295061da546Spatrick }
296061da546Spatrick
OnRunPacketSent(bool first)297061da546Spatrick void GDBRemoteClientBase::OnRunPacketSent(bool first) {
298061da546Spatrick if (first)
299061da546Spatrick BroadcastEvent(eBroadcastBitRunPacketSent, nullptr);
300061da546Spatrick }
301061da546Spatrick
302061da546Spatrick ///////////////////////////////////////
303061da546Spatrick // GDBRemoteClientBase::ContinueLock //
304061da546Spatrick ///////////////////////////////////////
305061da546Spatrick
ContinueLock(GDBRemoteClientBase & comm)306061da546Spatrick GDBRemoteClientBase::ContinueLock::ContinueLock(GDBRemoteClientBase &comm)
307061da546Spatrick : m_comm(comm), m_acquired(false) {
308061da546Spatrick lock();
309061da546Spatrick }
310061da546Spatrick
~ContinueLock()311061da546Spatrick GDBRemoteClientBase::ContinueLock::~ContinueLock() {
312061da546Spatrick if (m_acquired)
313061da546Spatrick unlock();
314061da546Spatrick }
315061da546Spatrick
unlock()316061da546Spatrick void GDBRemoteClientBase::ContinueLock::unlock() {
317061da546Spatrick lldbassert(m_acquired);
318061da546Spatrick {
319061da546Spatrick std::unique_lock<std::mutex> lock(m_comm.m_mutex);
320061da546Spatrick m_comm.m_is_running = false;
321061da546Spatrick }
322061da546Spatrick m_comm.m_cv.notify_all();
323061da546Spatrick m_acquired = false;
324061da546Spatrick }
325061da546Spatrick
326061da546Spatrick GDBRemoteClientBase::ContinueLock::LockResult
lock()327061da546Spatrick GDBRemoteClientBase::ContinueLock::lock() {
328*f6aab3d8Srobert Log *log = GetLog(GDBRLog::Process);
329061da546Spatrick LLDB_LOGF(log, "GDBRemoteClientBase::ContinueLock::%s() resuming with %s",
330061da546Spatrick __FUNCTION__, m_comm.m_continue_packet.c_str());
331061da546Spatrick
332061da546Spatrick lldbassert(!m_acquired);
333061da546Spatrick std::unique_lock<std::mutex> lock(m_comm.m_mutex);
334061da546Spatrick m_comm.m_cv.wait(lock, [this] { return m_comm.m_async_count == 0; });
335061da546Spatrick if (m_comm.m_should_stop) {
336061da546Spatrick m_comm.m_should_stop = false;
337061da546Spatrick LLDB_LOGF(log, "GDBRemoteClientBase::ContinueLock::%s() cancelled",
338061da546Spatrick __FUNCTION__);
339061da546Spatrick return LockResult::Cancelled;
340061da546Spatrick }
341061da546Spatrick if (m_comm.SendPacketNoLock(m_comm.m_continue_packet) !=
342061da546Spatrick PacketResult::Success)
343061da546Spatrick return LockResult::Failed;
344061da546Spatrick
345061da546Spatrick lldbassert(!m_comm.m_is_running);
346061da546Spatrick m_comm.m_is_running = true;
347061da546Spatrick m_acquired = true;
348061da546Spatrick return LockResult::Success;
349061da546Spatrick }
350061da546Spatrick
351061da546Spatrick ///////////////////////////////
352061da546Spatrick // GDBRemoteClientBase::Lock //
353061da546Spatrick ///////////////////////////////
354061da546Spatrick
Lock(GDBRemoteClientBase & comm,std::chrono::seconds interrupt_timeout)355be691f3bSpatrick GDBRemoteClientBase::Lock::Lock(GDBRemoteClientBase &comm,
356be691f3bSpatrick std::chrono::seconds interrupt_timeout)
357061da546Spatrick : m_async_lock(comm.m_async_mutex, std::defer_lock), m_comm(comm),
358be691f3bSpatrick m_interrupt_timeout(interrupt_timeout), m_acquired(false),
359be691f3bSpatrick m_did_interrupt(false) {
360be691f3bSpatrick SyncWithContinueThread();
361061da546Spatrick if (m_acquired)
362061da546Spatrick m_async_lock.lock();
363061da546Spatrick }
364061da546Spatrick
SyncWithContinueThread()365be691f3bSpatrick void GDBRemoteClientBase::Lock::SyncWithContinueThread() {
366*f6aab3d8Srobert Log *log = GetLog(GDBRLog::Process|GDBRLog::Packets);
367061da546Spatrick std::unique_lock<std::mutex> lock(m_comm.m_mutex);
368be691f3bSpatrick if (m_comm.m_is_running && m_interrupt_timeout == std::chrono::seconds(0))
369061da546Spatrick return; // We were asked to avoid interrupting the sender. Lock is not
370061da546Spatrick // acquired.
371061da546Spatrick
372061da546Spatrick ++m_comm.m_async_count;
373061da546Spatrick if (m_comm.m_is_running) {
374061da546Spatrick if (m_comm.m_async_count == 1) {
375061da546Spatrick // The sender has sent the continue packet and we are the first async
376061da546Spatrick // packet. Let's interrupt it.
377061da546Spatrick const char ctrl_c = '\x03';
378061da546Spatrick ConnectionStatus status = eConnectionStatusSuccess;
379061da546Spatrick size_t bytes_written = m_comm.Write(&ctrl_c, 1, status, nullptr);
380061da546Spatrick if (bytes_written == 0) {
381061da546Spatrick --m_comm.m_async_count;
382061da546Spatrick LLDB_LOGF(log, "GDBRemoteClientBase::Lock::Lock failed to send "
383061da546Spatrick "interrupt packet");
384061da546Spatrick return;
385061da546Spatrick }
386be691f3bSpatrick m_comm.m_interrupt_endpoint = steady_clock::now() + m_interrupt_timeout;
387061da546Spatrick if (log)
388061da546Spatrick log->PutCString("GDBRemoteClientBase::Lock::Lock sent packet: \\x03");
389061da546Spatrick }
390061da546Spatrick m_comm.m_cv.wait(lock, [this] { return !m_comm.m_is_running; });
391061da546Spatrick m_did_interrupt = true;
392061da546Spatrick }
393061da546Spatrick m_acquired = true;
394061da546Spatrick }
395061da546Spatrick
~Lock()396061da546Spatrick GDBRemoteClientBase::Lock::~Lock() {
397061da546Spatrick if (!m_acquired)
398061da546Spatrick return;
399061da546Spatrick {
400061da546Spatrick std::unique_lock<std::mutex> lock(m_comm.m_mutex);
401061da546Spatrick --m_comm.m_async_count;
402061da546Spatrick }
403061da546Spatrick m_comm.m_cv.notify_one();
404061da546Spatrick }
405