// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "platform/globals.h" #if defined(DART_HOST_OS_WINDOWS) #include "bin/eventhandler.h" #include "bin/eventhandler_win.h" #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include #include "bin/builtin.h" #include "bin/dartutils.h" #include "bin/lockers.h" #include "bin/process.h" #include "bin/socket.h" #include "bin/thread.h" #include "bin/utils.h" #include "platform/syslog.h" #include "platform/utils.h" namespace dart { namespace bin { // kBufferSize must be >= kMaxUDPPackageLength so that a complete UDP packet // can fit in the buffer. static constexpr int kBufferSize = 64 * 1024; static constexpr int kStdOverlappedBufferSize = 16 * 1024; static constexpr int kMaxUDPPackageLength = 64 * 1024; // For AcceptEx there needs to be buffer storage for address // information for two addresses (local and remote address). The // AcceptEx documentation says: "This value must be at least 16 // bytes more than the maximum address length for the transport // protocol in use." static constexpr int kAcceptExAddressAdditionalBytes = 16; static constexpr int kAcceptExAddressStorageSize = sizeof(SOCKADDR_STORAGE) + kAcceptExAddressAdditionalBytes; static constexpr intptr_t kOutEventMask = 1 << kOutEvent; static constexpr intptr_t kInEventMask = 1 << kInEvent; static bool DispatchEventIfEnabled(Handle* handle, intptr_t event_mask) { if ((handle->Mask() & event_mask) != 0) { DartUtils::PostInt32(handle->NextNotifyDartPort(event_mask), event_mask); return true; } return false; } static bool DispatchOutEventIfEnabled(Handle* handle) { return DispatchEventIfEnabled(handle, kOutEventMask); } static bool DispatchInEventIfEnabled(Handle* handle) { return DispatchEventIfEnabled(handle, kInEventMask); } OverlappedBuffer::OverlappedBuffer(Handle* handle, int buffer_size, Operation operation) : buflen_(buffer_size), operation_(operation), handle_(handle) { memset(GetBufferStart(), 0, GetBufferSize()); if (operation == kRecvFrom) { // Reserve part of the buffer for the length of source address. ASSERT(buflen_ > sizeof(RawAddr)); buflen_ -= sizeof(RawAddr); from_ = reinterpret_cast(GetBufferStart() + GetBufferSize()); // WSARecv needs to know how many bytes it can write. from_->size = sizeof(sockaddr_storage); } else { from_ = nullptr; } index_ = 0; data_length_ = 0; // Retain handle for the duration of the operation. handle->Retain(); } OverlappedBuffer::~OverlappedBuffer() { // If handle was not detached from the buffer release the reference // we were holding to it. if (handle_ != nullptr) { handle_->Release(); } if (client_ != INVALID_SOCKET) { closesocket(client_); } } std::unique_ptr OverlappedBuffer::AllocateBuffer( Handle* handle, int buffer_size, Operation operation) { OverlappedBuffer* buffer = new (buffer_size) OverlappedBuffer(handle, buffer_size, operation); return std::unique_ptr{buffer}; } std::unique_ptr OverlappedBuffer::AllocateAcceptBuffer( Handle* handle, ADDRESS_FAMILY sa_family) { auto result = AllocateBuffer(handle, 2 * kAcceptExAddressStorageSize, kAccept); result->client_ = socket(sa_family, SOCK_STREAM, sa_family == AF_UNIX ? 0 : IPPROTO_TCP); return result; } std::unique_ptr OverlappedBuffer::AllocateReadBuffer( Handle* handle, int buffer_size) { return AllocateBuffer(handle, buffer_size, kRead); } std::unique_ptr OverlappedBuffer::AllocateRecvFromBuffer( Handle* handle, int buffer_size) { // For calling recvfrom additional buffer space is needed for the source // address information. buffer_size += sizeof(RawAddr); return AllocateBuffer(handle, buffer_size, kRecvFrom); } std::unique_ptr OverlappedBuffer::AllocateWriteBuffer( Handle* handle, int buffer_size) { return AllocateBuffer(handle, buffer_size, kWrite); } std::unique_ptr OverlappedBuffer::AllocateSendToBuffer( Handle* handle, int buffer_size) { return AllocateBuffer(handle, buffer_size, kSendTo); } std::unique_ptr OverlappedBuffer::AllocateDisconnectBuffer( Handle* handle) { return AllocateBuffer(handle, 0, kDisconnect); } std::unique_ptr OverlappedBuffer::AllocateConnectBuffer( Handle* handle) { return AllocateBuffer(handle, 0, kConnect); } // CONTAINING_RECORD definition from winnt.h triggers ubsan // "member access within null pointer of type" error. #define CONTAINING_RECORD(address, type, field) \ reinterpret_cast((PCHAR)(address) - (ULONG_PTR)offsetof(type, field)) OverlappedBuffer* OverlappedBuffer::GetFromOverlapped(OVERLAPPED* overlapped) { OverlappedBuffer* buffer = CONTAINING_RECORD(overlapped, OverlappedBuffer, overlapped_); return buffer; } int OverlappedBuffer::Read(void* buffer, int num_bytes) { if (num_bytes > GetRemainingLength()) { num_bytes = GetRemainingLength(); } memmove(buffer, GetBufferStart() + index_, num_bytes); index_ += num_bytes; return num_bytes; } int OverlappedBuffer::Write(const void* buffer, int num_bytes) { ASSERT(num_bytes == buflen_); memmove(GetBufferStart(), buffer, num_bytes); data_length_ = num_bytes; return num_bytes; } int OverlappedBuffer::GetRemainingLength() { ASSERT(operation_ == kRead || operation_ == kRecvFrom); return data_length_ - index_; } Handle::Handle(intptr_t handle, Type type, Handle::SupportsOverlappedIO supports_overlapped_io /* = kYes */) : ReferenceCounted(), DescriptorInfoBase(handle), monitor_(), type_(type), handle_(reinterpret_cast(handle)), data_ready_() { if (supports_overlapped_io == SupportsOverlappedIO::kYes) { EventHandler::delegate()->AssociateWithCompletionPort(this); } else { flags_ |= 1 << kDoesNotSupportOverlappedIO; } } Handle::~Handle() {} void Handle::Close() { MonitorLocker ml(&monitor_); CloseLocked(&ml); } void Handle::CloseLocked(MonitorLocker* ml) { if (!supports_overlapped_io()) { // If the handle uses synchronous I/O (e.g. stdin), cancel any pending // operation before closing the handle, so the read thread is not blocked. BOOL result = CancelIoEx(handle_, nullptr); ASSERT(result || (GetLastError() == ERROR_NOT_FOUND)); } if (!IsClosing()) { // Close the socket and set the closing state. This close method can be // called again if this socket has pending IO operations in flight. MarkClosing(); // Perform handle type specific closing. DoCloseLocked(ml); } ASSERT(IsHandleClosed()); } void Handle::DoCloseLocked(MonitorLocker* ml) { if (!IsHandleClosed()) { CloseHandle(handle_); handle_ = INVALID_HANDLE_VALUE; } } bool Handle::HasPendingRead() { return pending_read_ != nullptr; } bool Handle::HasPendingWrite() { return pending_write_ != nullptr; } void Handle::WaitForReadThreadStarted() { MonitorLocker ml(&monitor_); while (read_thread_starting_) { ml.Wait(); } } void Handle::WaitForReadThreadFinished() { HANDLE to_join = nullptr; { MonitorLocker ml(&monitor_); if (read_thread_ != INVALID_HANDLE_VALUE) { while (!read_thread_finished_) { ml.Wait(); } read_thread_finished_ = false; to_join = read_thread_; read_thread_ = INVALID_HANDLE_VALUE; } } if (to_join != nullptr) { // Join the read thread. DWORD res = WaitForSingleObject(to_join, INFINITE); CloseHandle(to_join); ASSERT(res == WAIT_OBJECT_0); } } void Handle::ReadComplete(std::unique_ptr buffer) { WaitForReadThreadStarted(); { MonitorLocker ml(&monitor_); // Currently only one outstanding read at the time. ASSERT(pending_read_ == buffer.get()); ASSERT(data_ready_ == nullptr); if (!IsClosing()) { data_ready_ = std::move(buffer); } pending_read_ = nullptr; } WaitForReadThreadFinished(); } void Handle::WriteComplete(std::unique_ptr buffer) { MonitorLocker ml(&monitor_); // Currently only one outstanding write at the time. ASSERT(pending_write_ == buffer.get()); pending_write_ = nullptr; } // Helper method which returns a real HANDLE for the current thread. static HANDLE GetCurrentThreadHandle() { HANDLE thread_handle = INVALID_HANDLE_VALUE; if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &thread_handle, /*dwDesiredAccess=*/0, FALSE, DUPLICATE_SAME_ACCESS)) { FATAL("Failed to obtain thread handle"); } return thread_handle; } void Handle::NotifyReadThreadStarted() { MonitorLocker ml(&monitor_); ASSERT(read_thread_starting_); ASSERT(read_thread_ == INVALID_HANDLE_VALUE); read_thread_ = GetCurrentThreadHandle(); read_thread_starting_ = false; ml.Notify(); } void Handle::NotifyReadThreadFinished() { MonitorLocker ml(&monitor_); ASSERT(!read_thread_finished_); ASSERT(read_thread_ != INVALID_HANDLE_VALUE); read_thread_finished_ = true; ml.Notify(); } void Handle::ReadSyncCompleteAsync() { NotifyReadThreadStarted(); ASSERT(HasPendingRead()); ASSERT(pending_read_->GetBufferSize() >= kStdOverlappedBufferSize); DWORD buffer_size = pending_read_->GetBufferSize(); if (GetFileType(handle_) == FILE_TYPE_CHAR) { buffer_size = kStdOverlappedBufferSize; } char* buffer_start = pending_read_->GetBufferStart(); DWORD bytes_read = 0; BOOL ok = ::ReadFile(handle_, buffer_start, buffer_size, &bytes_read, nullptr); if (!ok) { bytes_read = 0; } OVERLAPPED* overlapped = pending_read_->GetCleanOverlapped(); ok = PostQueuedCompletionStatus(EventHandler::delegate()->completion_port(), bytes_read, reinterpret_cast(this), overlapped); if (!ok) { FATAL("PostQueuedCompletionStatus failed"); } NotifyReadThreadFinished(); } bool Handle::IssueReadLocked(MonitorLocker* ml) { ASSERT(type_ != kListenSocket); ASSERT(!HasPendingRead()); auto buffer = OverlappedBuffer::AllocateReadBuffer(this, kBufferSize); // Must initialize pending_read_ before issuing async operation, because // completion will race with this code. pending_read_ = buffer.get(); if (supports_overlapped_io()) { BOOL ok = ::ReadFile(handle_, buffer->GetBufferStart(), buffer->GetBufferSize(), nullptr, buffer->GetCleanOverlapped()); if (ok || (GetLastError() == ERROR_IO_PENDING)) { // Completing asynchronously. buffer.release(); // HandleIOCompletion will take ownership. return true; } pending_read_ = nullptr; HandleIssueError(); return false; } else { // Completing asynchronously through thread. Retain(); buffer.release(); // HandleIOCompletion will take ownership. read_thread_starting_ = true; Thread::Start( "dart:io ReadFile", [](uword args) { auto handle = reinterpret_cast(args); handle->ReadSyncCompleteAsync(); handle->Release(); }, reinterpret_cast(this)); return true; } } bool Handle::IssueRecvFromLocked(MonitorLocker* ml) { return false; } bool Handle::IssueWriteLocked(MonitorLocker* ml, std::unique_ptr buffer) { ASSERT(type_ != kListenSocket); ASSERT(!HasPendingWrite()); ASSERT(buffer->operation() == OverlappedBuffer::kWrite); // Must initialize pending_write_ before issuing asynchronous operation, // because completion will race with this code. pending_write_ = buffer.get(); BOOL ok = ::WriteFile(handle_, buffer->GetBufferStart(), buffer->GetBufferSize(), nullptr, buffer->GetCleanOverlapped()); if (ok || (GetLastError() == ERROR_IO_PENDING)) { // Completing asynchronously. buffer.release(); // HandleIOCompletion will take ownership. return true; } pending_write_ = nullptr; HandleIssueError(); return false; } bool Handle::IssueSendToLocked(MonitorLocker* ml, std::unique_ptr buffer, const RawAddr& addr) { return false; } static void HandleClosed(Handle* handle) { if (!handle->IsClosing()) { handle->NotifyAllDartPorts(1 << kCloseEvent); } } static void HandleError(Handle* handle) { handle->set_last_error(WSAGetLastError()); handle->MarkError(); if (!handle->IsClosing()) { handle->NotifyAllDartPorts(1 << kErrorEvent); } } void Handle::HandleIssueError() { DWORD error = GetLastError(); if (error == ERROR_BROKEN_PIPE) { HandleClosed(this); } else { HandleError(this); } SetLastError(error); } bool FileHandle::IsClosed() { return IsClosing() && !HasPendingRead() && !HasPendingWrite(); } void DirectoryWatchHandle::Start() { MonitorLocker ml(&monitor_); IssueReadLocked(&ml); } bool DirectoryWatchHandle::IsClosed() { return IsClosing() && !HasPendingRead(); } bool DirectoryWatchHandle::IssueReadLocked(MonitorLocker* ml) { // It may have been started before, as we start the directory-handler when // we create it. if (HasPendingRead() || (data_ready_ != nullptr)) { return true; } auto buffer = OverlappedBuffer::AllocateReadBuffer(this, kBufferSize); // Set up pending_read_ before ReadDirectoryChangesW because it might be // needed in ReadComplete invoked on event loop thread right away if data is // also ready right away. pending_read_ = buffer.get(); BOOL ok = ReadDirectoryChangesW( handle_, buffer->GetBufferStart(), buffer->GetBufferSize(), recursive_, events_, nullptr, buffer->GetCleanOverlapped(), nullptr); if (ok || (GetLastError() == ERROR_IO_PENDING)) { // Completing asynchronously. buffer.release(); // HandleIOCompletion will take ownership. return true; } pending_read_ = nullptr; return false; } void DirectoryWatchHandle::Stop() { MonitorLocker ml(&monitor_); // Stop the outstanding read, so we can close the handle. if (HasPendingRead()) { CancelIoEx(handle(), pending_read_->GetCleanOverlapped()); // Don't dispose of the buffer, as it will still complete (with length 0). } DoCloseLocked(&ml); } void SocketHandle::HandleIssueError() { int error = WSAGetLastError(); if (error == WSAECONNRESET) { HandleClosed(this); } else { HandleError(this); } WSASetLastError(error); } bool ListenSocket::IssueAcceptLocked(MonitorLocker* ml) { auto buffer = OverlappedBuffer::AllocateAcceptBuffer(this, sa_family_); DWORD received; BOOL ok; ok = EventHandler::delegate()->accept_ex()( socket(), buffer->client(), buffer->GetBufferStart(), 0, // For now don't receive data with accept. kAcceptExAddressStorageSize, kAcceptExAddressStorageSize, &received, buffer->GetCleanOverlapped()); if (ok || WSAGetLastError() == WSA_IO_PENDING) { pending_accept_count_++; buffer.release(); // HandleIOCompletion will take ownership. return true; } HandleError(this); return false; } bool ListenSocket::StartAccept() { MonitorLocker ml(&monitor_); // Always keep 5 outstanding accepts going, to enhance performance. for (intptr_t i = 0; i < kMinIssuedAccepts; i++) { if (!IssueAcceptLocked(&ml)) { return false; } } return true; } void ListenSocket::AcceptComplete(std::unique_ptr buffer) { MonitorLocker ml(&monitor_); if (!IsClosing()) { // Update the accepted socket to support the full range of API calls. SOCKET s = socket(); int rc = setsockopt(buffer->client(), SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, reinterpret_cast(&s), sizeof(s)); if (rc == NO_ERROR) { SOCKET client = buffer->client(); buffer->DetachClient(); // getpeername() returns incorrect results when used with a socket that // was accepted using overlapped I/O. AcceptEx includes the remote // address in its result so retrieve it using GetAcceptExSockaddrs and // save it. LPSOCKADDR local_addr; int local_addr_length; LPSOCKADDR remote_addr; int remote_addr_length; EventHandler::delegate()->get_accept_ex_sockaddrs()( buffer->GetBufferStart(), 0, kAcceptExAddressStorageSize, kAcceptExAddressStorageSize, &local_addr, &local_addr_length, &remote_addr, &remote_addr_length); auto raw_remote_addr = std::make_unique(); memmove(&raw_remote_addr->addr, remote_addr, remote_addr_length); raw_remote_addr->size = remote_addr_length; // Insert the accepted socket into the list. ClientSocket* client_socket = new ClientSocket(client, std::move(raw_remote_addr)); client_socket->mark_connected(); if (accepted_head_ == nullptr) { accepted_head_ = client_socket; accepted_tail_ = client_socket; } else { ASSERT(accepted_tail_ != nullptr); accepted_tail_->set_next(client_socket); accepted_tail_ = client_socket; } accepted_count_++; } } pending_accept_count_--; DispatchCompletedAcceptsLocked(&ml); } void ListenSocket::DispatchCompletedAcceptsLocked(MonitorLocker* ml) { if (IsClosing()) { return; } for (int i = 0; i < accepted_count(); i++) { if (!DispatchInEventIfEnabled(this)) { break; } } } static void NotifyDestroyedIfClosed(Handle* handle) { if (handle->IsClosed()) { handle->NotifyAllDartPorts(1 << kDestroyedEvent); handle->RemoveAllPorts(); } } void ListenSocket::DoCloseLocked(MonitorLocker* ml) { closesocket(socket()); handle_ = INVALID_HANDLE_VALUE; // Get rid of connections already accepted. ClientSocket* next_client = accepted_head_; while (next_client != nullptr) { ClientSocket* client = next_client; next_client = client->next(); client->set_next(nullptr); client->Close(); NotifyDestroyedIfClosed(client); client->Release(); } accepted_head_ = accepted_tail_ = nullptr; accepted_count_ = 0; } ClientSocket* ListenSocket::Accept() { MonitorLocker ml(&monitor_); ClientSocket* result = nullptr; if (accepted_head_ != nullptr) { result = accepted_head_; accepted_head_ = accepted_head_->next(); if (accepted_head_ == nullptr) { accepted_tail_ = nullptr; } result->set_next(nullptr); accepted_count_--; } // We have less than 5 pending accepts and are not closing try to queue // another accept. if (!IsClosing() && (pending_accept_count_ < kMinIssuedAccepts)) { IssueAcceptLocked(&ml); } return result; } bool ListenSocket::IsClosed() { return IsClosing() && !HasPendingAccept(); } intptr_t Handle::Available() { MonitorLocker ml(&monitor_); if (data_ready_ == nullptr) { return 0; } return data_ready_->GetRemainingLength(); } bool Handle::DataReady() { return data_ready_ != nullptr; } intptr_t Handle::Read(void* buffer, intptr_t num_bytes) { MonitorLocker ml(&monitor_); if (data_ready_ == nullptr) { return 0; } num_bytes = data_ready_->Read(buffer, Utils::Minimum(num_bytes, INT_MAX)); if (data_ready_->IsEmpty()) { data_ready_ = nullptr; if (!IsClosing() && !IsClosedRead()) { IssueReadLocked(&ml); } } return num_bytes; } intptr_t Handle::RecvFrom(void* buffer, intptr_t num_bytes, RawAddr* from) { MonitorLocker ml(&monitor_); if (data_ready_ == nullptr) { return 0; } num_bytes = data_ready_->Read(buffer, Utils::Minimum(num_bytes, INT_MAX)); *from = *data_ready_->from(); // Always dispose of the buffer, as UDP messages must be read in their // entirety to match how recvfrom works in a socket. data_ready_ = nullptr; if (!IsClosing() && !IsClosedRead()) { IssueRecvFromLocked(&ml); } return num_bytes; } intptr_t Handle::Write(const void* data, intptr_t num_bytes) { MonitorLocker ml(&monitor_); if (HasPendingWrite() || IsClosed()) { return 0; } if (num_bytes > kBufferSize) { num_bytes = kBufferSize; } ASSERT(supports_overlapped_io()); int truncated_bytes = Utils::Minimum(num_bytes, INT_MAX); auto buffer = OverlappedBuffer::AllocateWriteBuffer(this, truncated_bytes); buffer->Write(data, truncated_bytes); if (!IssueWriteLocked(&ml, std::move(buffer))) { return -1; } return truncated_bytes; } intptr_t Handle::SendTo(const void* data, intptr_t num_bytes, const RawAddr& addr) { MonitorLocker ml(&monitor_); if (HasPendingWrite() || IsClosed()) { return 0; } if (num_bytes > kBufferSize) { ASSERT(kBufferSize >= kMaxUDPPackageLength); // The provided buffer is larger than the maximum UDP datagram size so // return an error immediately. If the buffer were larger and the data were // actually passed to `WSASendTo()` then the operation would fail with // ERROR_INVALID_USER_BUFFER anyway. SetLastError(ERROR_INVALID_USER_BUFFER); return -1; } auto buffer = OverlappedBuffer::AllocateSendToBuffer(this, num_bytes); buffer->Write(data, num_bytes); if (!IssueSendToLocked(&ml, std::move(buffer), addr)) { return -1; } return num_bytes; } Mutex* StdHandle::stdin_mutex_ = new Mutex(); StdHandle* StdHandle::stdin_ = nullptr; StdHandle* StdHandle::Stdin(HANDLE handle) { MutexLocker ml(stdin_mutex_); if (stdin_ == nullptr) { stdin_ = new StdHandle(handle); } return stdin_; } void StdHandle::RunWriteLoop() { MonitorLocker ml(&monitor_); write_thread_running_ = true; thread_handle_ = GetCurrentThreadHandle(); // Notify we have started. ml.Notify(); while (write_thread_running_) { ml.Wait(Monitor::kNoTimeout); if (HasPendingWrite()) { // We woke up and had a pending write. Execute it. WriteSyncCompleteAsync(); } } write_thread_exists_ = false; ml.Notify(); } void StdHandle::WriteSyncCompleteAsync() { ASSERT(HasPendingWrite()); DWORD bytes_written = -1; BOOL ok = ::WriteFile(handle_, pending_write_->GetBufferStart(), pending_write_->GetBufferSize(), &bytes_written, nullptr); if (!ok) { bytes_written = 0; } thread_wrote_ += bytes_written; OVERLAPPED* overlapped = pending_write_->GetCleanOverlapped(); ok = PostQueuedCompletionStatus( EventHandler::delegate()->completion_port(), bytes_written, reinterpret_cast(this), overlapped); if (!ok) { FATAL("PostQueuedCompletionStatus failed"); } } intptr_t StdHandle::Write(const void* buffer, intptr_t num_bytes) { MonitorLocker ml(&monitor_); if (HasPendingWrite()) { return 0; } if (num_bytes > kBufferSize) { num_bytes = kBufferSize; } // In the case of stdout and stderr, OverlappedIO is not supported. // Here we'll instead use a thread, to make it async. // This code is actually never exposed to the user, as stdout and stderr is // not available as a RawSocket, but only wrapped in a Socket. // Note that we return '0', unless a thread have already completed a write. if (thread_wrote_ > 0) { if (num_bytes > thread_wrote_) { num_bytes = thread_wrote_; } thread_wrote_ -= num_bytes; return num_bytes; } if (!write_thread_exists_) { write_thread_exists_ = true; // The write thread gets a reference to the Handle, which it places in // the events it puts on the IO completion port. Retain(); Thread::Start( "dart:io WriteFile", [](uword args) { auto handle = reinterpret_cast(args); handle->RunWriteLoop(); handle->Release(); }, reinterpret_cast(this)); while (!write_thread_running_) { // Wait until we the thread is running. ml.Wait(Monitor::kNoTimeout); } } // Only queue up to INT_MAX bytes. int truncated_bytes = Utils::Minimum(num_bytes, INT_MAX); // Create buffer and notify thread about the new handle. pending_write_ = OverlappedBuffer::AllocateWriteBuffer(this, truncated_bytes).release(); pending_write_->Write(buffer, truncated_bytes); ml.Notify(); return 0; } void StdHandle::DoCloseLocked(MonitorLocker* ml) { if (write_thread_exists_) { write_thread_running_ = false; ml->Notify(); while (write_thread_exists_) { ml->Wait(Monitor::kNoTimeout); } // Join the thread. DWORD res = WaitForSingleObject(thread_handle_, INFINITE); CloseHandle(thread_handle_); ASSERT(res == WAIT_OBJECT_0); } Handle::DoCloseLocked(ml); MutexLocker stdin_mutex_locker(stdin_mutex_); stdin_->Release(); StdHandle::stdin_ = nullptr; } #if defined(DEBUG) intptr_t ClientSocket::disconnecting_ = 0; #endif void ClientSocket::Shutdown(int how) { shutdown(socket(), how); if (how == SD_RECEIVE) { MarkClosedRead(); } if (how == SD_SEND) { MarkClosedWrite(); } if (how == SD_BOTH) { MarkClosedRead(); MarkClosedWrite(); } } void ClientSocket::DoCloseLocked(MonitorLocker* ml) { // Always do a shutdown before initiating a disconnect. shutdown(socket(), SD_BOTH); IssueDisconnectLocked(ml); handle_ = INVALID_HANDLE_VALUE; } bool ClientSocket::IssueReadLocked(MonitorLocker* ml) { ASSERT(!HasPendingRead()); // TODO(sgjesse): Use a MTU value here. Only the loopback adapter can // handle 64k datagrams. auto buffer = OverlappedBuffer::AllocateReadBuffer(this, 65536); DWORD flags; flags = 0; pending_read_ = buffer.get(); int rc = WSARecv(socket(), buffer->GetWASBUF(), 1, nullptr, &flags, buffer->GetCleanOverlapped(), nullptr); if ((rc == NO_ERROR) || (WSAGetLastError() == WSA_IO_PENDING)) { buffer.release(); // HandleIOCompletion will take ownership. return true; } pending_read_ = nullptr; HandleIssueError(); return false; } bool ClientSocket::IssueWriteLocked(MonitorLocker* ml, std::unique_ptr buffer) { ASSERT(!HasPendingWrite()); ASSERT(buffer->operation() == OverlappedBuffer::kWrite); pending_write_ = buffer.get(); int rc = WSASend(socket(), pending_write_->GetWASBUF(), 1, nullptr, 0, pending_write_->GetCleanOverlapped(), nullptr); if ((rc == NO_ERROR) || (WSAGetLastError() == WSA_IO_PENDING)) { buffer.release(); // HandleIOCompletion will take ownership. return true; } pending_write_ = nullptr; HandleIssueError(); return false; } void ClientSocket::IssueDisconnectLocked(MonitorLocker* ml) { auto buffer = OverlappedBuffer::AllocateDisconnectBuffer(this); BOOL ok = EventHandler::delegate()->disconnect_ex()( socket(), buffer->GetCleanOverlapped(), TF_REUSE_SOCKET, 0); // DisconnectEx works like other OverlappedIO APIs, where we can get either an // immediate success or delayed operation by WSA_IO_PENDING being set. if (ok || (WSAGetLastError() != WSA_IO_PENDING)) { DisconnectComplete(); } else { // Completing asynchronously. buffer.release(); // HandleIOCompletion will take ownership. } // When the Dart side receives this event, it may decide to close its Dart // ports. When all ports are closed, the VM will shut down. The EventHandler // will then shut down. If the EventHandler shuts down before this // asynchronous disconnect finishes, this ClientSocket will be leaked. // TODO(dart:io): Retain a list of client sockets that are in the process of // disconnecting. Disconnect them forcefully, and clean up their resources // when the EventHandler shuts down. NotifyAllDartPorts(1 << kDestroyedEvent); RemoveAllPorts(); #if defined(DEBUG) disconnecting_++; #endif } void ClientSocket::DisconnectComplete() { closesocket(socket()); data_ready_ = nullptr; mark_closed(); #if defined(DEBUG) disconnecting_--; #endif } void ClientSocket::ConnectComplete() { // Update socket to support full socket API, after ConnectEx completed. setsockopt(socket(), SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nullptr, 0); // If the port is set, we already listen for this socket in Dart. // Handle the cases here. if (!IsClosedRead() && ((Mask() & kInEventMask) != 0)) { MonitorLocker ml(&monitor_); IssueReadLocked(&ml); } if (!IsClosedWrite()) { DispatchOutEventIfEnabled(this); } } bool ClientSocket::IsClosed() { return connected_ && closed_ && !HasPendingRead() && !HasPendingWrite(); } bool ClientSocket::PopulateRemoteAddr(RawAddr& addr) { if (!remote_addr_) { return false; } addr = *remote_addr_; return true; } bool DatagramSocket::IssueSendToLocked(MonitorLocker* ml, std::unique_ptr buffer, const RawAddr& addr) { ASSERT(!HasPendingWrite()); ASSERT(buffer->operation() == OverlappedBuffer::kSendTo); pending_write_ = buffer.get(); int rc = WSASendTo(socket(), pending_write_->GetWASBUF(), 1, nullptr, 0, &addr.addr, addr.size, pending_write_->GetCleanOverlapped(), nullptr); if ((rc == NO_ERROR) || (WSAGetLastError() == WSA_IO_PENDING)) { buffer.release(); // HandleIOCompletion will take ownership. return true; } pending_write_ = nullptr; HandleIssueError(); return false; } bool DatagramSocket::IssueRecvFromLocked(MonitorLocker* ml) { ASSERT(!HasPendingRead()); auto buffer = OverlappedBuffer::AllocateRecvFromBuffer(this, kMaxUDPPackageLength); pending_read_ = buffer.get(); DWORD flags = 0; int rc = WSARecvFrom(socket(), buffer->GetWASBUF(), 1, nullptr, &flags, &buffer->from()->addr, &buffer->from()->size, buffer->GetCleanOverlapped(), nullptr); if ((rc == NO_ERROR) || (WSAGetLastError() == WSA_IO_PENDING)) { buffer.release(); // HandleIOCompletion will take ownership. return true; } pending_read_ = nullptr; HandleIssueError(); return false; } bool DatagramSocket::IsClosed() { return IsClosing() && !HasPendingRead() && !HasPendingWrite(); } void DatagramSocket::DoCloseLocked(MonitorLocker* ml) { // Just close the socket. This will cause any queued requests to be aborted. closesocket(socket()); MarkClosedRead(); MarkClosedWrite(); handle_ = INVALID_HANDLE_VALUE; } void EventHandlerImplementation::HandleInterrupt(InterruptMessage* msg) { if (msg->id == kTimerId) { // Change of timeout request. Just set the new timeout and port as the // completion thread will use the new timeout value for its next wait. timeout_queue_.UpdateTimeout(msg->dart_port, msg->data); } else if (msg->id == kShutdownId) { shutdown_ = true; } else { Socket* socket = reinterpret_cast(msg->id); RefCntReleaseScope rs(socket); if (socket->fd() == -1) { return; } Handle* handle = reinterpret_cast(socket->fd()); ASSERT(handle != nullptr); handle->Retain(); RefCntReleaseScope rh(handle); MonitorLocker hl(&handle->monitor_); switch (msg->data & COMMAND_MASK) { case 1 << kReturnTokenCommand: handle->ReturnTokens(msg->dart_port, TOKEN_COUNT(msg->data)); break; case 1 << kSetEventMaskCommand: { // `events` can only have kInEvent/kOutEvent flags set. intptr_t events = msg->data & EVENT_MASK; ASSERT(0 == (events & ~(kInEventMask | kOutEventMask))); handle->SetPortAndMask(msg->dart_port, events); if (handle->is_listen_socket()) { static_cast(handle)->DispatchCompletedAcceptsLocked( &hl); } else { // Issue a read. if ((handle->Mask() & kInEventMask) != 0) { if (handle->is_datagram_socket()) { handle->IssueRecvFromLocked(&hl); } else if (!handle->is_client_socket() || reinterpret_cast(handle) ->is_connected()) { handle->IssueReadLocked(&hl); } } // If out events (can write events) have been requested, and there // are no pending writes, meaning any writes are already complete, // post an out event immediately. // // Client sockets are only notified if they are connected. if ((events & kOutEventMask) != 0 && !handle->HasPendingWrite()) { if (!handle->is_client_socket() || reinterpret_cast(handle)->is_connected()) { DispatchOutEventIfEnabled(handle); } } // Similarly, if in events (can read events) have been requested, and // there is pending data available, post an in event immediately. if ((events & kInEventMask) != 0) { if (handle->data_ready_ != nullptr && !handle->data_ready_->IsEmpty()) { DispatchInEventIfEnabled(handle); } } } break; } case 1 << kShutdownReadCommand: { ASSERT(handle->is_client_socket()); ClientSocket* client_socket = reinterpret_cast(handle); client_socket->Shutdown(SD_RECEIVE); break; } case 1 << kShutdownWriteCommand: { ASSERT(handle->is_client_socket()); ClientSocket* client_socket = reinterpret_cast(handle); client_socket->Shutdown(SD_SEND); break; } case 1 << kCloseCommand: { if (IS_SIGNAL_SOCKET(msg->data)) { Process::ClearSignalHandlerByFd(socket->fd(), socket->isolate_port()); } bool can_close_handle = true; if (handle->is_listen_socket()) { // We only close the socket file descriptor if there are no other // dart socket objects which are listening on the same // (address, port) combination. ListeningSocketRegistry* registry = ListeningSocketRegistry::Instance(); MutexLocker locker(registry->mutex()); if (!registry->CloseSafe(socket)) { // Other sockets are listening to the same OS socket. Do not close // OS socket, but deassociate it from the message port and tell // Dart side that this socket was destroyed. can_close_handle = false; handle->RemovePort(msg->dart_port); DartUtils::PostInt32(msg->dart_port, 1 << kDestroyedEvent); socket->SetClosedFd(); } } if (can_close_handle) { // Set response port so that kDestroyedEvent notification is // delivered to the listener. handle->SetPortAndMask(msg->dart_port, 0); handle->CloseLocked(&hl); socket->CloseFd(); } break; } default: UNREACHABLE(); break; } NotifyDestroyedIfClosed(handle); } } void EventHandlerImplementation::HandleAccept( ListenSocket* listen_socket, std::unique_ptr buffer) { listen_socket->AcceptComplete(std::move(buffer)); } void EventHandlerImplementation::HandleRead( Handle* handle, int bytes, std::unique_ptr buffer) { buffer->set_data_length(bytes); handle->ReadComplete(std::move(buffer)); if (bytes > 0) { if (!handle->IsClosing()) { DispatchInEventIfEnabled(handle); } } else { handle->MarkClosedRead(); if (bytes == 0) { HandleClosed(handle); } else { HandleError(handle); } } } void EventHandlerImplementation::HandleRecvFrom( Handle* handle, int bytes, std::unique_ptr buffer) { ASSERT(handle->is_datagram_socket()); if (bytes >= 0) { buffer->set_data_length(bytes); handle->ReadComplete(std::move(buffer)); if (!handle->IsClosing()) { DispatchInEventIfEnabled(handle); } } else { HandleError(handle); } } void EventHandlerImplementation::HandleWrite( Handle* handle, int bytes, std::unique_ptr buffer) { handle->WriteComplete(std::move(buffer)); if (bytes >= 0) { if (!handle->IsError() && !handle->IsClosing()) { ASSERT(!handle->is_client_socket() || reinterpret_cast(handle)->is_connected()); DispatchOutEventIfEnabled(handle); } } else { HandleError(handle); } } void EventHandlerImplementation::HandleDisconnect( ClientSocket* client_socket, int bytes, std::unique_ptr buffer) { client_socket->DisconnectComplete(); } void EventHandlerImplementation::HandleConnect( ClientSocket* client_socket, int bytes, std::unique_ptr buffer) { if (bytes < 0) { HandleError(client_socket); } else { client_socket->ConnectComplete(); } client_socket->mark_connected(); } void EventHandlerImplementation::HandleTimeout() { if (!timeout_queue_.HasTimeout()) { return; } DartUtils::PostNull(timeout_queue_.CurrentPort()); timeout_queue_.RemoveCurrent(); } static const char* OperationName(OverlappedBuffer::Operation op) { switch (op) { case OverlappedBuffer::kAccept: return "Accept"; case OverlappedBuffer::kRead: return "Read"; case OverlappedBuffer::kRecvFrom: return "RecvFrom"; case OverlappedBuffer::kWrite: return "Write"; case OverlappedBuffer::kSendTo: return "SendTo"; case OverlappedBuffer::kDisconnect: return "Disconnect"; case OverlappedBuffer::kConnect: return "Connect"; } return "?"; } void EventHandlerImplementation::HandleIOCompletion(int32_t bytes, ULONG_PTR key, OVERLAPPED* overlapped) { std::unique_ptr buffer( OverlappedBuffer::GetFromOverlapped(overlapped)); Handle* handle = reinterpret_cast(key); RefCntReleaseScope release(buffer->StealHandle()); switch (buffer->operation()) { case OverlappedBuffer::kAccept: { HandleAccept(static_cast(handle), std::move(buffer)); break; } case OverlappedBuffer::kRead: { HandleRead(handle, bytes, std::move(buffer)); break; } case OverlappedBuffer::kRecvFrom: { HandleRecvFrom(handle, bytes, std::move(buffer)); break; } case OverlappedBuffer::kWrite: case OverlappedBuffer::kSendTo: { HandleWrite(handle, bytes, std::move(buffer)); break; } case OverlappedBuffer::kDisconnect: { HandleDisconnect(static_cast(handle), bytes, std::move(buffer)); break; } case OverlappedBuffer::kConnect: { HandleConnect(static_cast(handle), bytes, std::move(buffer)); break; } default: UNREACHABLE(); } NotifyDestroyedIfClosed(handle); } void EventHandlerImplementation::HandleCompletionOrInterrupt( BOOL ok, DWORD bytes, ULONG_PTR key, OVERLAPPED* overlapped) { if (!ok) { // Treat ERROR_CONNECTION_ABORTED as connection closed. // The error ERROR_OPERATION_ABORTED is set for pending // accept requests for a listen socket which is closed. // ERROR_NETNAME_DELETED occurs when the client closes // the socket it is reading from. DWORD last_error = GetLastError(); if ((last_error == ERROR_CONNECTION_ABORTED) || (last_error == ERROR_OPERATION_ABORTED) || (last_error == ERROR_NETNAME_DELETED) || (last_error == ERROR_BROKEN_PIPE)) { ASSERT(bytes == 0); HandleIOCompletion(bytes, key, overlapped); } else if (last_error == ERROR_MORE_DATA) { // Don't ASSERT no bytes in this case. This can happen if the receive // buffer for datagram sockets is too small to contain a full datagram, // and in this case bytes hold the bytes that was read. HandleIOCompletion(-1, key, overlapped); } else { ASSERT(bytes == 0); HandleIOCompletion(-1, key, overlapped); } } else if (key == NULL) { // A key of nullptr signals an interrupt message. InterruptMessage* msg = reinterpret_cast(overlapped); HandleInterrupt(msg); delete msg; } else { HandleIOCompletion(bytes, key, overlapped); } } EventHandlerImplementation::EventHandlerImplementation() { completion_port_ = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, NULL, 1); if (completion_port_ == nullptr) { FATAL("Completion port creation failed"); } } namespace { template void GetSocketExtensionFunction(SOCKET socket, GUID guid, F* result) { DWORD bytes; int status = WSAIoctl(socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), result, sizeof(F), &bytes, nullptr, nullptr); if (status == SOCKET_ERROR) { FATAL("Failed to get a pointer to the extension function."); } } } // namespace void EventHandlerImplementation::InitializeSocketExtensions() { if (socket_extensions_initialized_.load()) { return; } MonitorLocker ml(&monitor_); SOCKET dummy = socket(AF_INET, SOCK_STREAM, 0); GetSocketExtensionFunction(dummy, WSAID_ACCEPTEX, &accept_ex_); GetSocketExtensionFunction(dummy, WSAID_CONNECTEX, &connect_ex_); GetSocketExtensionFunction(dummy, WSAID_DISCONNECTEX, &disconnect_ex_); GetSocketExtensionFunction(dummy, WSAID_GETACCEPTEXSOCKADDRS, &get_accept_ex_sockaddrs_); socket_extensions_initialized_.store(true); closesocket(dummy); } EventHandlerImplementation::~EventHandlerImplementation() { // Join the handler thread. DWORD res = WaitForSingleObject(handler_thread_, INFINITE); CloseHandle(handler_thread_); ASSERT(res == WAIT_OBJECT_0); CloseHandle(completion_port_); } void EventHandlerImplementation::AssociateWithCompletionPort(Handle* handle) { HANDLE result = CreateIoCompletionPort(handle->handle(), completion_port_, reinterpret_cast(handle), 0); if (result == nullptr) { FATAL("Failed to associate handle with completion port"); } } int64_t EventHandlerImplementation::GetTimeout() { if (!timeout_queue_.HasTimeout()) { return kInfinityTimeout; } int64_t millis = timeout_queue_.CurrentTimeout() - TimerUtils::GetCurrentMonotonicMillis(); return (millis < 0) ? 0 : millis; } void EventHandlerImplementation::SendData(intptr_t id, Dart_Port dart_port, int64_t data) { InterruptMessage* msg = new InterruptMessage; msg->id = id; msg->dart_port = dart_port; msg->data = data; BOOL ok = PostQueuedCompletionStatus(completion_port_, 0, NULL, reinterpret_cast(msg)); if (!ok) { FATAL("PostQueuedCompletionStatus failed"); } } void EventHandlerImplementation::EventHandlerEntry(uword args) { EventHandler* handler = reinterpret_cast(args); EventHandlerImplementation* handler_impl = &handler->delegate_; ASSERT(handler_impl != nullptr); { MonitorLocker ml(&handler_impl->monitor_); handler_impl->handler_thread_ = GetCurrentThreadHandle(); ml.Notify(); } DWORD bytes; ULONG_PTR key; OVERLAPPED* overlapped; BOOL ok; while (!handler_impl->shutdown_) { int64_t millis = handler_impl->GetTimeout(); ASSERT(millis == kInfinityTimeout || millis >= 0); if (millis > kMaxInt32) { millis = kMaxInt32; } ASSERT(sizeof(int32_t) == sizeof(DWORD)); DWORD timeout = static_cast(millis); ok = GetQueuedCompletionStatus(handler_impl->completion_port(), &bytes, &key, &overlapped, timeout); if (!ok && (overlapped == nullptr)) { if (GetLastError() == ERROR_ABANDONED_WAIT_0) { // The completion port should never be closed. Syslog::Print("Completion port closed\n"); UNREACHABLE(); } else { // Timeout is signalled by false result and nullptr in overlapped. handler_impl->HandleTimeout(); } } else { handler_impl->HandleCompletionOrInterrupt(ok, bytes, key, overlapped); } } // In a Debug build, drain the IO completion port to make sure we aren't // leaking any (non-disconnecting) Handles. In a Release build, we don't care // because the VM is going down, and the asserts below are Debug-only. #if defined(DEBUG) while (true) { ok = GetQueuedCompletionStatus(handler_impl->completion_port(), &bytes, &key, &overlapped, 0); if (!ok && (overlapped == nullptr)) { // There was an error or nothing is ready. Assume the port is drained. break; } handler_impl->HandleCompletionOrInterrupt(ok, bytes, key, overlapped); } // The eventhandler thread is going down so there should be no more live // Handles or Sockets. // TODO(dart:io): It would be nice to be able to assert here that: // ReferenceCounted::instances() == 0; // However, we cannot at the moment. See the TODO on: // ClientSocket::IssueDisconnect() // Furthermore, if the Dart program references stdin, but does not // explicitly close it, then the StdHandle for it will be leaked to here. const intptr_t stdin_leaked = (StdHandle::StdinPtr() == nullptr) ? 0 : 1; DEBUG_ASSERT(ReferenceCounted::instances() == ClientSocket::disconnecting() + stdin_leaked); DEBUG_ASSERT(ReferenceCounted::instances() == 0); #endif // defined(DEBUG) handler->NotifyShutdownDone(); } void EventHandlerImplementation::Start(EventHandler* handler) { Thread::Start("dart:io EventHandler", EventHandlerEntry, reinterpret_cast(handler)); { MonitorLocker ml(&monitor_); while (handler_thread_ == INVALID_HANDLE_VALUE) { ml.Wait(); } } } void EventHandlerImplementation::Shutdown() { SendData(kShutdownId, 0, 0); } } // namespace bin } // namespace dart #endif // defined(DART_HOST_OS_WINDOWS)