master
h 832 lines 26.2 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 SocketChannel.h
8
9 Abstract:
10
11 This file contains the SocketChannel helper class implementation.
12
13 --*/
14
15 #pragma once
16
17 #include <atomic>
18 #include <mutex>
19 #include "socketshared.h"
20 #include "lxinitshared.h"
21
22 #ifndef WIN32
23 #include <assert.h>
24 #include "lxwil.h"
25 #include "../../linux/init/util.h"
26 extern std::optional<bool> g_EnableSocketLogging;
27 #endif
28
29 namespace wsl::shared {
30 #ifdef WIN32
31
32 using TSocket = wil::unique_socket;
33 using TTimeout = DWORD;
34
35 constexpr DWORD DefaultSocketTimeout = INFINITE;
36
37 #else
38
39 using TSocket = wil::unique_fd;
40 using TTimeout = const timeval*;
41 constexpr timeval* DefaultSocketTimeout = nullptr;
42
43 #endif
44
45 class SocketChannel;
46
47 class Transaction
48 {
49 friend class SocketChannel;
50
51 public:
52 ~Transaction() = default;
53
54 NON_COPYABLE(Transaction);
55
56 template <typename TMessage>
57 void Send(gsl::span<gsl::byte> span);
58
59 template <typename TMessage>
60 void Send(TMessage& message);
61
62 template <typename TResult>
63 void SendResultMessage(TResult value);
64
65 template <typename TMessage>
66 std::pair<TMessage*, gsl::span<gsl::byte>> ReceiveOrClosed();
67
68 template <typename TMessage>
69 TMessage& Receive(gsl::span<gsl::byte>* responseSpan = nullptr);
70
71 private:
72 Transaction(SocketChannel& channel, uint32_t id, TTimeout timeout) :
73 m_channel(channel), m_id(id), m_step(static_cast<uint32_t>(TRANSACTION_STEP::REQUEST)), m_deadline(ComputeDeadline(timeout))
74 {
75 }
76
77 TTimeout RemainingTimeout() const;
78
79 static std::optional<std::chrono::steady_clock::time_point> ComputeDeadline(TTimeout timeout);
80
81 SocketChannel& m_channel;
82 uint32_t m_id;
83 /** Use uint32_t as step can go beyond FIRST_REPLY */
84 uint32_t m_step;
85 std::optional<std::chrono::steady_clock::time_point> m_deadline;
86
87 #ifndef WIN32
88 // This is required because the Linux timeout logic requires a pointer. Returning a pointer is OK because only thread can use a transaction at a given time.
89 mutable timeval m_timeoutStorage{};
90 #endif
91 };
92
93 class SocketChannel
94 {
95
96 public:
97 SocketChannel() = default;
98
99 SocketChannel(const SocketChannel&) = delete;
100 SocketChannel(SocketChannel&& other)
101 {
102 *this = std::move(other);
103 }
104
105 SocketChannel& operator=(const SocketChannel&) = delete;
106 SocketChannel& operator=(SocketChannel&& other)
107 {
108 m_name = std::move(other.m_name);
109 m_socket = std::move(other.m_socket);
110
111 #ifdef WIN32
112 m_exitEvents = std::move(other.m_exitEvents);
113 m_pendingBytes = std::move(other.m_pendingBytes);
114 #endif
115 m_ignore_sequence = other.m_ignore_sequence;
116 m_sent_non_transaction_messages = other.m_sent_non_transaction_messages;
117 m_received_non_transaction_messages = other.m_received_non_transaction_messages;
118 m_transaction_id_seed = other.m_transaction_id_seed.load();
119
120 return *this;
121 }
122
123 SocketChannel(TSocket&& socket, std::string&& name) : m_socket(std::move(socket)), m_name(std::move(name))
124 {
125 }
126
127 #ifdef WIN32
128
129 SocketChannel(TSocket&& socket, std::string&& name, std::vector<HANDLE>&& exitEvents) :
130 m_socket(std::move(socket)), m_exitEvents(std::move(exitEvents)), m_name(std::move(name))
131 {
132 }
133
134 std::vector<HANDLE> SetExitEvents(std::vector<HANDLE>&& exitEvents)
135 {
136 auto oldExitEvents = std::move(m_exitEvents);
137 m_exitEvents = std::move(exitEvents);
138 return oldExitEvents;
139 }
140
141 const std::vector<HANDLE>& GetExitEvents() const
142 {
143 return m_exitEvents;
144 }
145
146 #endif
147
148 template <typename TMessage>
149 void SendMessage(gsl::span<gsl::byte> span, uint32_t transactionStep = static_cast<uint32_t>(TRANSACTION_STEP::NONE), uint32_t transactionId = 0, TTimeout timeout = DefaultSocketTimeout)
150 {
151 // Ensure that no other thread is using this channel.
152 const std::unique_lock<std::mutex> lock{m_sendMutex, std::try_to_lock};
153 if (!lock.owns_lock())
154 {
155
156 #ifdef WIN32
157
158 THROW_HR_MSG(E_UNEXPECTED, "Incorrect channel usage detected on channel: %hs, message type: %hs", m_name.c_str(), ToString(TMessage::Type));
159
160 #else
161
162 LOG_ERROR("Incorrect channel usage detected on channel: {}, message type: {}", m_name, ToString(TMessage::Type));
163 THROW_ERRNO(EINVAL);
164
165 #endif
166 }
167
168 THROW_INVALID_ARG_IF(m_name.empty() || span.size() < sizeof(TMessage));
169
170 auto* header = gslhelpers::try_get_struct<MESSAGE_HEADER>(span);
171 WI_ASSERT(header->MessageSize == span.size());
172
173 if (transactionStep == static_cast<unsigned int>(TRANSACTION_STEP::NONE))
174 {
175 m_sent_non_transaction_messages++;
176 header->TransactionId = m_sent_non_transaction_messages;
177 header->TransactionStep = static_cast<unsigned int>(TRANSACTION_STEP::NONE);
178 }
179 else
180 {
181 header->TransactionId = transactionId;
182 header->TransactionStep = transactionStep;
183 }
184
185 #ifdef WIN32
186
187 auto io = CreateIO();
188 io.AddHandle(std::make_unique<windows::common::io::WriteHandle>(m_socket.get(), span));
189
190 io.Run(TimeoutToMilliseconds(timeout));
191
192 WSL_LOG(
193 "SentMessage",
194 TraceLoggingValue(m_name.c_str(), "Name"),
195 TraceLoggingValue(reinterpret_cast<const TMessage*>(span.data())->PrettyPrint().c_str(), "Content"));
196
197 #else
198
199 if (LoggingEnabled())
200 {
201 LOG_INFO("SentMessage on channel: {}: '{}'", m_name, reinterpret_cast<const TMessage*>(span.data())->PrettyPrint().c_str());
202 }
203
204 if (UtilWriteBuffer(m_socket.get(), span.data(), span.size()) < 0)
205 {
206 LOG_ERROR("Failed to write message {}. Channel: {}", header->MessageType, m_name);
207 THROW_LAST_ERROR();
208 }
209
210 #endif
211 }
212
213 template <typename TMessage>
214 MESSAGE_HEADER& GetMessageHeader(TMessage& message)
215 {
216 if constexpr (std::is_same_v<TMessage, MESSAGE_HEADER>)
217 {
218 return message;
219 }
220 else
221 {
222 return message.Header;
223 }
224 }
225
226 template <typename TMessage>
227 void SendMessage()
228 {
229 TMessage message;
230 SendMessage(message);
231 }
232
233 template <typename TMessage>
234 void SendMessage(TMessage& message, uint32_t transactionStep = static_cast<uint32_t>(TRANSACTION_STEP::NONE), uint32_t transactionId = 0, TTimeout timeout = DefaultSocketTimeout)
235 {
236 // Catch situations where the other SendMessage() method should be used
237 const auto& header = GetMessageHeader(message);
238 if (header.MessageSize != sizeof(message))
239 {
240 #ifdef WIN32
241 THROW_HR_MSG(E_INVALIDARG, "Incorrect header size for message type: %u on channel: %hs", header.MessageType, m_name.c_str());
242 #else
243 LOG_ERROR("Incorrect header size for message type: {} on channel: {}", header.MessageType, m_name);
244 THROW_ERRNO(EINVAL);
245 #endif
246 }
247
248 SendMessage<TMessage>(gslhelpers::struct_as_writeable_bytes(message), transactionStep, transactionId, timeout);
249 }
250
251 template <typename TResult>
252 void SendResultMessage(TResult value)
253 {
254 RESULT_MESSAGE<TResult> Result{};
255 Result.Header.MessageSize = sizeof(Result);
256 Result.Header.MessageType = RESULT_MESSAGE<TResult>::Type;
257 Result.Result = value;
258
259 SendMessage(Result);
260 }
261
262 template <typename TMessage>
263 std::pair<TMessage*, gsl::span<gsl::byte>> ReceiveMessageOrClosed(
264 TTimeout timeout = DefaultSocketTimeout,
265 uint32_t expectedTransactionStep = static_cast<uint32_t>(TRANSACTION_STEP::NONE),
266 uint32_t expectedTransactionId = 0)
267 {
268 WI_ASSERT(!m_name.empty());
269
270 // Ensure that no other thread is using this channel.
271 const std::unique_lock<std::mutex> lock{m_receiveMutex, std::try_to_lock};
272 if (!lock.owns_lock())
273 {
274
275 #ifdef WIN32
276
277 THROW_HR_MSG(E_UNEXPECTED, "Incorrect channel usage detected on channel: %hs", m_name.c_str());
278 #else
279
280 LOG_ERROR("Incorrect channel usage detected on channel: {}", m_name);
281 THROW_ERRNO(EINVAL);
282
283 #endif
284 }
285
286 gsl::span<gsl::byte> receivedSpan{};
287 for (;;)
288 {
289 if (expectedTransactionStep == static_cast<uint32_t>(TRANSACTION_STEP::NONE))
290 {
291 // Adhere to the old ++ before receive behavior for non-transaction messages.
292 m_received_non_transaction_messages++;
293 }
294
295 receivedSpan = ReceiveImpl(timeout);
296 if (receivedSpan.empty())
297 {
298
299 #ifdef WIN32
300 if (errno == HCS_E_CONNECTION_TIMEOUT)
301 {
302 THROW_HR_MSG(
303 HCS_E_CONNECTION_TIMEOUT,
304 "Timeout: %u, expected type: %hs, channel: %hs",
305 timeout,
306 ToString(TMessage::Type),
307 m_name.c_str());
308 }
309 #endif
310
311 return {nullptr, {}};
312 }
313
314 auto* header = gslhelpers::try_get_struct<MESSAGE_HEADER>(receivedSpan);
315 if (header == nullptr)
316 {
317 #ifdef WIN32
318 THROW_HR_MSG(E_UNEXPECTED, "Message too small for header: %zd, channel: %hs", receivedSpan.size(), m_name.c_str());
319 #else
320 LOG_ERROR("Message too small for header: {}, channel: {}", receivedSpan.size(), m_name);
321 THROW_ERRNO(EINVAL);
322 #endif
323 }
324
325 if (expectedTransactionStep == static_cast<uint32_t>(TRANSACTION_STEP::NONE))
326 {
327 // Handle non-transaction messages with legacy logic.
328 if (!m_ignore_sequence)
329 {
330 if (header->TransactionStep != static_cast<unsigned int>(TRANSACTION_STEP::NONE))
331 {
332 #ifdef WIN32
333 THROW_HR_MSG(
334 E_UNEXPECTED,
335 "Unexpected transaction message received on non-transaction channel: %hs, message type: %hs",
336 m_name.c_str(),
337 ToString(header->MessageType));
338 #else
339 LOG_ERROR(
340 "Unexpected transaction message received on non-transaction channel: {}, message type: {}",
341 m_name,
342 ToString(header->MessageType));
343 THROW_ERRNO(EINVAL);
344 #endif
345 }
346 if (header->TransactionId != m_received_non_transaction_messages)
347 {
348 #ifdef WIN32
349 THROW_HR_MSG(
350 E_UNEXPECTED,
351 "Unexpected non-transaction message id: %u, expected: %u, channel: %hs",
352 header->TransactionId,
353 m_received_non_transaction_messages,
354 m_name.c_str());
355 #else
356 LOG_ERROR("Unexpected non-transaction message id: {}, expected: {}, channel: {}", header->TransactionId, m_received_non_transaction_messages, m_name);
357 THROW_ERRNO(EINVAL);
358 #endif
359 }
360 }
361 break;
362 }
363
364 // Handle transaction messages
365 if (header->TransactionStep == static_cast<uint32_t>(TRANSACTION_STEP::NONE))
366 {
367 // Skip stale non-transaction messages
368 #ifdef WIN32
369 WSL_LOG(
370 "DiscardStaleNonTransactionMessage",
371 TraceLoggingValue(m_name.c_str(), "Name"),
372 TraceLoggingValue(ToString(header->MessageType), "MessageType"),
373 TraceLoggingValue(ToString(TMessage::Type), "ExpectedMessageType"),
374 TraceLoggingValue(header->TransactionId, "StaleNonTransactionId"),
375 TraceLoggingValue(m_received_non_transaction_messages, "ExpectedNonTransactionId"));
376 #else
377 LOG_WARNING(
378 "Discard stale non-transaction message on channel: {}. MessageType: {}, ExpectedMessageType: {}, "
379 "StaleNonTransactionId: {}, ExpectedNonTransactionId: {}",
380 m_name,
381 header->MessageType,
382 TMessage::Type,
383 header->TransactionId,
384 m_received_non_transaction_messages);
385 #endif
386 continue;
387 }
388
389 if (expectedTransactionStep == static_cast<uint32_t>(TRANSACTION_STEP::REQUEST))
390 {
391 // Skip until we get the next request. No matter the transaction id.
392 if (header->TransactionStep != static_cast<unsigned int>(TRANSACTION_STEP::REQUEST))
393 {
394 #ifdef WIN32
395 WSL_LOG(
396 "DiscardOutOfOrderTransactionMessage",
397 TraceLoggingValue(m_name.c_str(), "Name"),
398 TraceLoggingValue(ToString(header->MessageType), "MessageType"),
399 TraceLoggingValue(ToString(TMessage::Type), "ExpectedMessageType"),
400 TraceLoggingValue(header->TransactionStep, "StaleTransactionStep"),
401 TraceLoggingValue(expectedTransactionStep, "ExpectedTransactionStep"));
402 #else
403 LOG_WARNING(
404 "Discard out of order transaction message on channel: {}. MessageType: {}, ExpectedMessageType: {}, "
405 "StaleTransactionStep: {}, ExpectedTransactionStep: {}",
406 m_name,
407 header->MessageType,
408 TMessage::Type,
409 header->TransactionStep,
410 expectedTransactionStep);
411 #endif
412 continue;
413 }
414 break;
415 }
416
417 auto diff = static_cast<int32_t>(header->TransactionId - expectedTransactionId);
418 if (diff < 0)
419 {
420 // Skip stale transaction messages
421 #ifdef WIN32
422 WSL_LOG(
423 "DiscardStaleTransactionMessage",
424 TraceLoggingValue(m_name.c_str(), "Name"),
425 TraceLoggingValue(ToString(header->MessageType), "MessageType"),
426 TraceLoggingValue(ToString(TMessage::Type), "ExpectedMessageType"),
427 TraceLoggingValue(header->TransactionId, "StaleTransactionId"),
428 TraceLoggingValue(expectedTransactionId, "ExpectedTransactionId"));
429 #else
430 LOG_WARNING(
431 "Discard stale transaction message on channel: {}. MessageType: {}, ExpectedMessageType: {}, "
432 "StaleTransactionId: {}, ExpectedTransactionId: {}",
433 m_name,
434 header->MessageType,
435 TMessage::Type,
436 header->TransactionId,
437 expectedTransactionId);
438 #endif
439 continue;
440 }
441
442 if (diff > 0)
443 {
444 // Message is from the future.
445 #ifdef WIN32
446 THROW_HR_MSG(
447 E_UNEXPECTED,
448 "Unexpected transaction message id: %u, expected: %u, channel: %hs",
449 header->TransactionId,
450 expectedTransactionId,
451 m_name.c_str());
452 #else
453 LOG_ERROR("Unexpected transaction message id: {}, expected: {}, channel: {}", header->TransactionId, expectedTransactionId, m_name);
454 THROW_ERRNO(EINVAL);
455 #endif
456 }
457
458 if (header->TransactionStep != expectedTransactionStep)
459 {
460 // Broken transaction.
461 #ifdef WIN32
462 THROW_HR_MSG(
463 E_UNEXPECTED,
464 "Unexpected transaction message step: %u, expected: %u, channel: %hs",
465 header->TransactionStep,
466 expectedTransactionStep,
467 m_name.c_str());
468 #else
469 LOG_ERROR("Unexpected transaction message step: {}, expected: {}, channel: {}", header->TransactionStep, expectedTransactionStep, m_name);
470 THROW_ERRNO(EINVAL);
471 #endif
472 }
473
474 break;
475 }
476
477 auto* message = gslhelpers::try_get_struct<TMessage>(receivedSpan);
478
479 if (message == nullptr)
480 {
481 #ifdef WIN32
482 THROW_HR_MSG(
483 E_UNEXPECTED,
484 "Message size is too small: %zd, expected type: %hs, channel: %hs",
485 receivedSpan.size(),
486 ToString(TMessage::Type),
487 m_name.c_str());
488 #else
489 LOG_ERROR("MessageSize is too small: {}, expected type: {}, channel: {}", receivedSpan.size(), ToString(TMessage::Type), m_name);
490 THROW_ERRNO(EINVAL);
491 #endif
492 }
493
494 ValidateMessageHeader(GetMessageHeader(*message), TMessage::Type);
495
496 #ifdef WIN32
497 WSL_LOG(
498 "ReceivedMessage",
499 TraceLoggingValue(m_name.c_str(), "Name"),
500 TraceLoggingValue(message->PrettyPrint().c_str(), "Content"));
501 #else
502 if (LoggingEnabled())
503 {
504 LOG_INFO("ReceivedMessage on channel: {}: '{}'", m_name, message->PrettyPrint().c_str());
505 }
506 #endif
507 return {message, receivedSpan};
508 }
509
510 template <typename TMessage>
511 TMessage& ReceiveMessage(
512 gsl::span<gsl::byte>* responseSpan = nullptr,
513 TTimeout timeout = DefaultSocketTimeout,
514 uint32_t expectedTransactionStep = static_cast<uint32_t>(TRANSACTION_STEP::NONE),
515 uint32_t expectedTransactionId = 0)
516 {
517 auto [message, span] = ReceiveMessageOrClosed<TMessage>(timeout, expectedTransactionStep, expectedTransactionId);
518 if (message == nullptr)
519 {
520 #ifdef WIN32
521 THROW_HR_MSG(E_UNEXPECTED, "Expected message %hs, but socket %hs was closed", ToString(TMessage::Type), m_name.c_str());
522 #else
523 LOG_ERROR("ExpectedMessage {}, but socket {} was closed", ToString(TMessage::Type), m_name);
524 THROW_ERRNO(EINVAL);
525 #endif
526 }
527
528 if (responseSpan != nullptr)
529 {
530 *responseSpan = span;
531 }
532
533 return *message;
534 }
535
536 Transaction StartTransaction(TTimeout timeout = DefaultSocketTimeout)
537 {
538 uint32_t transactionId = m_transaction_id_seed++;
539 return wsl::shared::Transaction(*this, transactionId, timeout);
540 }
541
542 Transaction ReceiveTransaction(TTimeout timeout = DefaultSocketTimeout)
543 {
544 // Transaction id should follow the received one on the receive end.
545 return wsl::shared::Transaction(*this, 0, timeout);
546 }
547
548 template <typename TSentMessage>
549 typename TSentMessage::TResponse& Transaction(gsl::span<gsl::byte> message, gsl::span<gsl::byte>* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout)
550 {
551 auto transaction = StartTransaction(timeout);
552 transaction.template Send<TSentMessage>(message);
553 return transaction.template Receive<typename TSentMessage::TResponse>(responseSpan);
554 }
555
556 template <typename TSentMessage>
557 typename TSentMessage::TResponse& Transaction(TSentMessage& message, gsl::span<gsl::byte>* responseSpan = nullptr, TTimeout timeout = DefaultSocketTimeout)
558 {
559 WI_ASSERT(message.Header.MessageSize == sizeof(message));
560
561 return Transaction<TSentMessage>(gslhelpers::struct_as_writeable_bytes(message), responseSpan, timeout);
562 }
563
564 template <typename TSentMessage>
565 TSentMessage::TResponse& Transaction(TTimeout timeout = DefaultSocketTimeout)
566 {
567 TSentMessage message{};
568 message.Header.MessageSize = sizeof(message);
569 message.Header.MessageType = TSentMessage::Type;
570 return Transaction<TSentMessage>(message, nullptr, timeout);
571 }
572
573 void Close()
574 {
575 m_socket.reset();
576 }
577
578 auto Socket() const
579 {
580 return m_socket.get();
581 }
582
583 auto Release()
584 {
585 return std::move(m_socket);
586 }
587
588 bool Connected() const
589 {
590 return m_socket.get() >= 0;
591 }
592
593 void IgnoreSequenceNumbers()
594 {
595 m_ignore_sequence = true;
596 }
597
598 #ifndef WIN32
599
600 static void EnableSocketLogging(bool enable)
601 {
602 g_EnableSocketLogging = enable;
603 }
604
605 #endif
606
607 private:
608 #ifdef WIN32
609 windows::common::io::MultiHandleWait CreateIO() const
610 {
611 wsl::windows::common::io::MultiHandleWait io;
612
613 for (const auto event : m_exitEvents)
614 {
615 io.AddHandle(
616 std::make_unique<windows::common::io::EventHandle>(
617 event,
618 [this, event]() { THROW_HR_MSG(E_ABORT, "Exit event 0x%p signaled on channel: %hs", event, m_name.c_str()); }),
619 windows::common::io::MultiHandleWait::CancelOnCompleted | windows::common::io::MultiHandleWait::NeedNotComplete);
620 }
621
622 return io;
623 }
624
625 static std::optional<std::chrono::milliseconds> TimeoutToMilliseconds(TTimeout timeout)
626 {
627 if (timeout == INFINITE)
628 {
629 return std::nullopt;
630 }
631
632 return std::chrono::milliseconds{timeout};
633 }
634
635 gsl::span<gsl::byte> ReceiveImpl(TTimeout timeout)
636 {
637 auto io = CreateIO();
638
639 gsl::span<gsl::byte> message;
640 io.AddHandle(std::make_unique<windows::common::io::ReadSocketMessageHandle>(
641 m_socket.get(), m_buffer, m_pendingBytes, [&message](auto& received) { message = received; }));
642
643 io.Run(TimeoutToMilliseconds(timeout));
644
645 return message;
646 }
647
648 #else
649
650 gsl::span<gsl::byte> ReceiveImpl(TTimeout timeout)
651 {
652 return wsl::shared::socket::RecvMessage(m_socket.get(), m_buffer, timeout);
653 }
654
655 #endif
656
657 void ValidateMessageHeader(const MESSAGE_HEADER& header, LX_MESSAGE_TYPE expected) const
658 {
659
660 if (header.MessageSize < sizeof(header) || (expected != LxMiniInitMessageAny && header.MessageType != expected))
661 {
662 #ifdef WIN32
663
664 THROW_HR_MSG(
665 E_UNEXPECTED,
666 "Protocol error: Received message size: %u, type: %u, id: %u, step: %u. Expected type: %u, "
667 "channel: %hs",
668 header.MessageSize,
669 header.MessageType,
670 header.TransactionId,
671 header.TransactionStep,
672 expected,
673 m_name.c_str());
674 #else
675
676 LOG_ERROR(
677 "Protocol error: Received message size: {}, type: {}, id: {}, step: {}. Expected type: {}, "
678 "channel: {}",
679 header.MessageSize,
680 header.MessageType,
681 header.TransactionId,
682 header.TransactionStep,
683 expected,
684 m_name);
685
686 THROW_ERRNO(EINVAL);
687
688 #endif
689 }
690 }
691
692 #ifndef WIN32
693
694 static bool LoggingEnabled()
695 {
696 static std::once_flag flag;
697 std::call_once(flag, [&]() {
698 try
699 {
700 if (g_EnableSocketLogging.has_value())
701 {
702 return;
703 }
704
705 auto content = UtilReadFileContent("/proc/cmdline");
706 g_EnableSocketLogging = content.find("WSL_SOCKET_LOG") != std::string::npos;
707 }
708 catch (...)
709 {
710 LOG_CAUGHT_EXCEPTION();
711 g_EnableSocketLogging = false;
712 }
713 });
714
715 return g_EnableSocketLogging.value();
716 }
717
718 #endif
719
720 TSocket m_socket{};
721 std::vector<gsl::byte> m_buffer;
722
723 #ifdef WIN32
724
725 std::vector<HANDLE> m_exitEvents;
726 std::vector<gsl::byte> m_pendingBytes;
727
728 #endif
729 uint32_t m_sent_non_transaction_messages = 0;
730 uint32_t m_received_non_transaction_messages = 0;
731 std::atomic<uint32_t> m_transaction_id_seed = 0;
732 bool m_ignore_sequence = false;
733 std::string m_name{};
734 std::mutex m_sendMutex;
735 std::mutex m_receiveMutex;
736 };
737
738 inline std::optional<std::chrono::steady_clock::time_point> Transaction::ComputeDeadline(TTimeout timeout)
739 {
740 #ifdef WIN32
741 if (timeout == INFINITE)
742 {
743 return std::nullopt;
744 }
745
746 return std::chrono::steady_clock::now() + std::chrono::milliseconds{timeout};
747 #else
748 if (timeout == nullptr)
749 {
750 return std::nullopt;
751 }
752
753 return std::chrono::steady_clock::now() + std::chrono::seconds{timeout->tv_sec} + std::chrono::microseconds{timeout->tv_usec};
754 #endif
755 }
756
757 inline TTimeout Transaction::RemainingTimeout() const
758 {
759 if (!m_deadline.has_value())
760 {
761 return DefaultSocketTimeout;
762 }
763
764 #ifdef WIN32
765
766 auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(*m_deadline - std::chrono::steady_clock::now());
767 return remaining.count() > 0 ? static_cast<DWORD>(remaining.count()) : 0;
768
769 #else
770
771 auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(*m_deadline - std::chrono::steady_clock::now());
772
773 m_timeoutStorage.tv_sec = static_cast<time_t>(remaining.count() / 1000);
774 m_timeoutStorage.tv_usec = static_cast<suseconds_t>((remaining.count() % 1000) * 1000);
775 return &m_timeoutStorage;
776
777 #endif
778 }
779
780 template <typename TMessage>
781 void Transaction::Send(gsl::span<gsl::byte> span)
782 {
783 m_channel.SendMessage<TMessage>(span, m_step, m_id, RemainingTimeout());
784 m_step++;
785 }
786
787 template <typename TMessage>
788 void Transaction::Send(TMessage& message)
789 {
790 Send<TMessage>(gslhelpers::struct_as_writeable_bytes(message));
791 }
792
793 template <typename TResult>
794 void Transaction::SendResultMessage(TResult value)
795 {
796 RESULT_MESSAGE<TResult> Result{};
797 Result.Header.MessageSize = sizeof(Result);
798 Result.Header.MessageType = RESULT_MESSAGE<TResult>::Type;
799 Result.Result = value;
800
801 Send(Result);
802 }
803
804 template <typename TMessage>
805 std::pair<TMessage*, gsl::span<gsl::byte>> Transaction::ReceiveOrClosed()
806 {
807 auto result = m_channel.ReceiveMessageOrClosed<TMessage>(RemainingTimeout(), m_step, m_id);
808 if (m_step == static_cast<uint32_t>(TRANSACTION_STEP::REQUEST) && result.first != nullptr)
809 {
810 // Use the request's id for the reply side transaction.
811 MESSAGE_HEADER& header = m_channel.GetMessageHeader(*result.first);
812 m_id = header.TransactionId;
813 }
814 m_step++;
815 return result;
816 }
817
818 template <typename TMessage>
819 TMessage& Transaction::Receive(gsl::span<gsl::byte>* responseSpan)
820 {
821 auto& message = m_channel.ReceiveMessage<TMessage>(responseSpan, RemainingTimeout(), m_step, m_id);
822 if (m_step == static_cast<uint32_t>(TRANSACTION_STEP::REQUEST))
823 {
824 // Use the request's id for the reply side transaction.
825 MESSAGE_HEADER& header = m_channel.GetMessageHeader(message);
826 m_id = header.TransactionId;
827 }
828 m_step++;
829 return message;
830 }
831
832 } // namespace wsl::shared