d11df78618
This required some minor changes to the Windows eventhandler, as we now spawn a new thread for writes (just like we do for reads). BUG=https://code.google.com/p/dart/issues/detail?id=7218 R=sgjesse@google.com Review URL: https://codereview.chromium.org//23609060 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@27811 260f80e4-7a28-3924-810f-c04153c831b5
1109 lines
31 KiB
C++
1109 lines
31 KiB
C++
// 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(TARGET_OS_WINDOWS)
|
|
|
|
#include "bin/eventhandler.h"
|
|
|
|
#include <process.h> // NOLINT
|
|
#include <winsock2.h> // NOLINT
|
|
#include <ws2tcpip.h> // NOLINT
|
|
#include <mswsock.h> // NOLINT
|
|
#include <io.h> // NOLINT
|
|
#include <fcntl.h> // NOLINT
|
|
|
|
#include "bin/builtin.h"
|
|
#include "bin/dartutils.h"
|
|
#include "bin/log.h"
|
|
#include "bin/socket.h"
|
|
#include "bin/utils.h"
|
|
#include "platform/thread.h"
|
|
|
|
|
|
namespace dart {
|
|
namespace bin {
|
|
|
|
static const int kBufferSize = 64 * 1024;
|
|
static const int kStdOverlappedBufferSize = 16 * 1024;
|
|
|
|
static const int kInfinityTimeout = -1;
|
|
static const int kTimeoutId = -1;
|
|
static const int kShutdownId = -2;
|
|
|
|
OverlappedBuffer* OverlappedBuffer::AllocateBuffer(int buffer_size,
|
|
Operation operation) {
|
|
OverlappedBuffer* buffer =
|
|
new(buffer_size) OverlappedBuffer(buffer_size, operation);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
OverlappedBuffer* OverlappedBuffer::AllocateAcceptBuffer(int buffer_size) {
|
|
OverlappedBuffer* buffer = AllocateBuffer(buffer_size, kAccept);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
OverlappedBuffer* OverlappedBuffer::AllocateReadBuffer(int buffer_size) {
|
|
return AllocateBuffer(buffer_size, kRead);
|
|
}
|
|
|
|
|
|
OverlappedBuffer* OverlappedBuffer::AllocateWriteBuffer(int buffer_size) {
|
|
return AllocateBuffer(buffer_size, kWrite);
|
|
}
|
|
|
|
|
|
OverlappedBuffer* OverlappedBuffer::AllocateDisconnectBuffer() {
|
|
return AllocateBuffer(0, kDisconnect);
|
|
}
|
|
|
|
|
|
void OverlappedBuffer::DisposeBuffer(OverlappedBuffer* buffer) {
|
|
delete buffer;
|
|
}
|
|
|
|
|
|
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);
|
|
return data_length_ - index_;
|
|
}
|
|
|
|
|
|
Handle::Handle(HANDLE handle)
|
|
: handle_(reinterpret_cast<HANDLE>(handle)),
|
|
port_(0),
|
|
mask_(0),
|
|
completion_port_(INVALID_HANDLE_VALUE),
|
|
event_handler_(NULL),
|
|
data_ready_(NULL),
|
|
pending_read_(NULL),
|
|
pending_write_(NULL),
|
|
last_error_(NOERROR),
|
|
thread_wrote_(0),
|
|
flags_(0) {
|
|
InitializeCriticalSection(&cs_);
|
|
}
|
|
|
|
|
|
Handle::Handle(HANDLE handle, Dart_Port port)
|
|
: handle_(reinterpret_cast<HANDLE>(handle)),
|
|
port_(port),
|
|
mask_(0),
|
|
completion_port_(INVALID_HANDLE_VALUE),
|
|
event_handler_(NULL),
|
|
data_ready_(NULL),
|
|
pending_read_(NULL),
|
|
pending_write_(NULL),
|
|
last_error_(NOERROR),
|
|
thread_wrote_(0),
|
|
flags_(0) {
|
|
InitializeCriticalSection(&cs_);
|
|
}
|
|
|
|
|
|
Handle::~Handle() {
|
|
DeleteCriticalSection(&cs_);
|
|
}
|
|
|
|
|
|
void Handle::Lock() {
|
|
EnterCriticalSection(&cs_);
|
|
}
|
|
|
|
|
|
void Handle::Unlock() {
|
|
LeaveCriticalSection(&cs_);
|
|
}
|
|
|
|
|
|
bool Handle::CreateCompletionPort(HANDLE completion_port) {
|
|
completion_port_ = CreateIoCompletionPort(handle_,
|
|
completion_port,
|
|
reinterpret_cast<ULONG_PTR>(this),
|
|
0);
|
|
if (completion_port_ == NULL) {
|
|
Log::PrintErr("Error CreateIoCompletionPort: %d\n", GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void Handle::Close() {
|
|
ScopedLock lock(this);
|
|
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.
|
|
ASSERT(handle_ != INVALID_HANDLE_VALUE);
|
|
MarkClosing();
|
|
// Perform handle type specific closing.
|
|
DoClose();
|
|
}
|
|
}
|
|
|
|
|
|
void Handle::DoClose() {
|
|
CloseHandle(handle_);
|
|
handle_ = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
|
|
bool Handle::HasPendingRead() {
|
|
ScopedLock lock(this);
|
|
return pending_read_ != NULL;
|
|
}
|
|
|
|
|
|
bool Handle::HasPendingWrite() {
|
|
ScopedLock lock(this);
|
|
return pending_write_ != NULL;
|
|
}
|
|
|
|
|
|
void Handle::ReadComplete(OverlappedBuffer* buffer) {
|
|
ScopedLock lock(this);
|
|
// Currently only one outstanding read at the time.
|
|
ASSERT(pending_read_ == buffer);
|
|
ASSERT(data_ready_ == NULL);
|
|
if (!IsClosing() && !buffer->IsEmpty()) {
|
|
data_ready_ = pending_read_;
|
|
} else {
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
}
|
|
pending_read_ = NULL;
|
|
}
|
|
|
|
|
|
void Handle::WriteComplete(OverlappedBuffer* buffer) {
|
|
ScopedLock lock(this);
|
|
// Currently only one outstanding write at the time.
|
|
ASSERT(pending_write_ == buffer);
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
pending_write_ = NULL;
|
|
}
|
|
|
|
|
|
static unsigned int __stdcall ReadFileThread(void* args) {
|
|
Handle* handle = reinterpret_cast<Handle*>(args);
|
|
handle->ReadSyncCompleteAsync();
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Handle::ReadSyncCompleteAsync() {
|
|
ASSERT(pending_read_ != NULL);
|
|
ASSERT(pending_read_->GetBufferSize() >= kStdOverlappedBufferSize);
|
|
|
|
DWORD buffer_size = pending_read_->GetBufferSize();
|
|
if (GetFileType(handle_) == FILE_TYPE_CHAR) {
|
|
buffer_size = kStdOverlappedBufferSize;
|
|
}
|
|
DWORD bytes_read = 0;
|
|
BOOL ok = ReadFile(handle_,
|
|
pending_read_->GetBufferStart(),
|
|
buffer_size,
|
|
&bytes_read,
|
|
NULL);
|
|
if (!ok) {
|
|
if (GetLastError() != ERROR_BROKEN_PIPE) {
|
|
Log::PrintErr("ReadFile failed %d\n", GetLastError());
|
|
}
|
|
bytes_read = 0;
|
|
}
|
|
OVERLAPPED* overlapped = pending_read_->GetCleanOverlapped();
|
|
ok = PostQueuedCompletionStatus(event_handler_->completion_port(),
|
|
bytes_read,
|
|
reinterpret_cast<ULONG_PTR>(this),
|
|
overlapped);
|
|
if (!ok) {
|
|
FATAL("PostQueuedCompletionStatus failed");
|
|
}
|
|
}
|
|
|
|
|
|
bool Handle::IssueRead() {
|
|
ScopedLock lock(this);
|
|
ASSERT(type_ != kListenSocket);
|
|
ASSERT(pending_read_ == NULL);
|
|
OverlappedBuffer* buffer = OverlappedBuffer::AllocateReadBuffer(kBufferSize);
|
|
if (SupportsOverlappedIO()) {
|
|
ASSERT(completion_port_ != INVALID_HANDLE_VALUE);
|
|
|
|
BOOL ok = ReadFile(handle_,
|
|
buffer->GetBufferStart(),
|
|
buffer->GetBufferSize(),
|
|
NULL,
|
|
buffer->GetCleanOverlapped());
|
|
if (ok || GetLastError() == ERROR_IO_PENDING) {
|
|
// Completing asynchronously.
|
|
pending_read_ = buffer;
|
|
return true;
|
|
}
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
HandleIssueError();
|
|
return false;
|
|
} else {
|
|
// Completing asynchronously through thread.
|
|
pending_read_ = buffer;
|
|
uint32_t tid;
|
|
uintptr_t thread_handle =
|
|
_beginthreadex(NULL, 32 * 1024, ReadFileThread, this, 0, &tid);
|
|
if (thread_handle == -1) {
|
|
FATAL("Failed to start read file thread");
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
bool Handle::IssueWrite() {
|
|
ScopedLock lock(this);
|
|
ASSERT(type_ != kListenSocket);
|
|
ASSERT(completion_port_ != INVALID_HANDLE_VALUE);
|
|
ASSERT(pending_write_ != NULL);
|
|
ASSERT(pending_write_->operation() == OverlappedBuffer::kWrite);
|
|
|
|
OverlappedBuffer* buffer = pending_write_;
|
|
BOOL ok = WriteFile(handle_,
|
|
buffer->GetBufferStart(),
|
|
buffer->GetBufferSize(),
|
|
NULL,
|
|
buffer->GetCleanOverlapped());
|
|
if (ok || GetLastError() == ERROR_IO_PENDING) {
|
|
// Completing asynchronously.
|
|
pending_write_ = buffer;
|
|
return true;
|
|
}
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
HandleIssueError();
|
|
return false;
|
|
}
|
|
|
|
|
|
void Handle::HandleIssueError() {
|
|
DWORD error = GetLastError();
|
|
if (error == ERROR_BROKEN_PIPE) {
|
|
event_handler_->HandleClosed(this);
|
|
} else {
|
|
event_handler_->HandleError(this);
|
|
}
|
|
SetLastError(error);
|
|
}
|
|
|
|
|
|
void FileHandle::EnsureInitialized(EventHandlerImplementation* event_handler) {
|
|
ScopedLock lock(this);
|
|
event_handler_ = event_handler;
|
|
if (SupportsOverlappedIO() && completion_port_ == INVALID_HANDLE_VALUE) {
|
|
CreateCompletionPort(event_handler_->completion_port());
|
|
}
|
|
}
|
|
|
|
|
|
bool FileHandle::IsClosed() {
|
|
return IsClosing() && !HasPendingRead() && !HasPendingWrite();
|
|
}
|
|
|
|
|
|
void FileHandle::DoClose() {
|
|
if (handle_ == GetStdHandle(STD_OUTPUT_HANDLE)) {
|
|
int fd = _open("NUL", _O_WRONLY);
|
|
ASSERT(fd >= 0);
|
|
_dup2(fd, _fileno(stdout));
|
|
close(fd);
|
|
} else {
|
|
Handle::DoClose();
|
|
}
|
|
}
|
|
|
|
|
|
void DirectoryWatchHandle::EnsureInitialized(
|
|
EventHandlerImplementation* event_handler) {
|
|
ScopedLock lock(this);
|
|
event_handler_ = event_handler;
|
|
if (completion_port_ == INVALID_HANDLE_VALUE) {
|
|
CreateCompletionPort(event_handler_->completion_port());
|
|
}
|
|
}
|
|
|
|
|
|
bool DirectoryWatchHandle::IsClosed() {
|
|
return IsClosing() && pending_read_ == NULL;
|
|
}
|
|
|
|
|
|
bool DirectoryWatchHandle::IssueRead() {
|
|
ScopedLock lock(this);
|
|
OverlappedBuffer* buffer = OverlappedBuffer::AllocateReadBuffer(kBufferSize);
|
|
|
|
ASSERT(completion_port_ != INVALID_HANDLE_VALUE);
|
|
|
|
BOOL ok = ReadDirectoryChangesW(handle_,
|
|
buffer->GetBufferStart(),
|
|
buffer->GetBufferSize(),
|
|
recursive_,
|
|
events_,
|
|
NULL,
|
|
buffer->GetCleanOverlapped(),
|
|
NULL);
|
|
if (ok || GetLastError() == ERROR_IO_PENDING) {
|
|
// Completing asynchronously.
|
|
pending_read_ = buffer;
|
|
return true;
|
|
}
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
return false;
|
|
}
|
|
|
|
|
|
void SocketHandle::HandleIssueError() {
|
|
int error = WSAGetLastError();
|
|
if (error == WSAECONNRESET) {
|
|
event_handler_->HandleClosed(this);
|
|
} else {
|
|
event_handler_->HandleError(this);
|
|
}
|
|
WSASetLastError(error);
|
|
}
|
|
|
|
|
|
bool ListenSocket::LoadAcceptEx() {
|
|
// Load the AcceptEx function into memory using WSAIoctl.
|
|
GUID guid_accept_ex = WSAID_ACCEPTEX;
|
|
DWORD bytes;
|
|
int status = WSAIoctl(socket(),
|
|
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
|
&guid_accept_ex,
|
|
sizeof(guid_accept_ex),
|
|
&AcceptEx_,
|
|
sizeof(AcceptEx_),
|
|
&bytes,
|
|
NULL,
|
|
NULL);
|
|
if (status == SOCKET_ERROR) {
|
|
Log::PrintErr("Error WSAIoctl failed: %d\n", WSAGetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ListenSocket::IssueAccept() {
|
|
ScopedLock lock(this);
|
|
|
|
// 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 const int kAcceptExAddressAdditionalBytes = 16;
|
|
static const int kAcceptExAddressStorageSize =
|
|
sizeof(SOCKADDR_STORAGE) + kAcceptExAddressAdditionalBytes;
|
|
OverlappedBuffer* buffer =
|
|
OverlappedBuffer::AllocateAcceptBuffer(2 * kAcceptExAddressStorageSize);
|
|
DWORD received;
|
|
BOOL ok;
|
|
ok = AcceptEx_(socket(),
|
|
buffer->client(),
|
|
buffer->GetBufferStart(),
|
|
0, // For now don't receive data with accept.
|
|
kAcceptExAddressStorageSize,
|
|
kAcceptExAddressStorageSize,
|
|
&received,
|
|
buffer->GetCleanOverlapped());
|
|
if (!ok) {
|
|
if (WSAGetLastError() != WSA_IO_PENDING) {
|
|
Log::PrintErr("AcceptEx failed: %d\n", WSAGetLastError());
|
|
closesocket(buffer->client());
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
pending_accept_count_++;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void ListenSocket::AcceptComplete(OverlappedBuffer* buffer,
|
|
HANDLE completion_port) {
|
|
ScopedLock lock(this);
|
|
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<char*>(&s), sizeof(s));
|
|
if (rc == NO_ERROR) {
|
|
// Insert the accepted socket into the list.
|
|
ClientSocket* client_socket = new ClientSocket(buffer->client(), 0);
|
|
client_socket->CreateCompletionPort(completion_port);
|
|
if (accepted_head_ == NULL) {
|
|
accepted_head_ = client_socket;
|
|
accepted_tail_ = client_socket;
|
|
} else {
|
|
ASSERT(accepted_tail_ != NULL);
|
|
accepted_tail_->set_next(client_socket);
|
|
accepted_tail_ = client_socket;
|
|
}
|
|
} else {
|
|
Log::PrintErr("setsockopt failed: %d\n", WSAGetLastError());
|
|
closesocket(buffer->client());
|
|
}
|
|
} else {
|
|
// Close the socket, as it's already accepted.
|
|
closesocket(buffer->client());
|
|
}
|
|
|
|
pending_accept_count_--;
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
}
|
|
|
|
|
|
void ListenSocket::DoClose() {
|
|
closesocket(socket());
|
|
handle_ = INVALID_HANDLE_VALUE;
|
|
while (CanAccept()) {
|
|
// Get rid of connections already accepted.
|
|
ClientSocket *client = Accept();
|
|
if (client != NULL) {
|
|
client->Close();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool ListenSocket::CanAccept() {
|
|
ScopedLock lock(this);
|
|
return accepted_head_ != NULL;
|
|
}
|
|
|
|
|
|
ClientSocket* ListenSocket::Accept() {
|
|
ScopedLock lock(this);
|
|
if (accepted_head_ == NULL) return NULL;
|
|
ClientSocket* result = accepted_head_;
|
|
accepted_head_ = accepted_head_->next();
|
|
if (accepted_head_ == NULL) accepted_tail_ = NULL;
|
|
result->set_next(NULL);
|
|
return result;
|
|
}
|
|
|
|
|
|
void ListenSocket::EnsureInitialized(
|
|
EventHandlerImplementation* event_handler) {
|
|
ScopedLock lock(this);
|
|
if (AcceptEx_ == NULL) {
|
|
ASSERT(completion_port_ == INVALID_HANDLE_VALUE);
|
|
ASSERT(event_handler_ == NULL);
|
|
event_handler_ = event_handler;
|
|
CreateCompletionPort(event_handler_->completion_port());
|
|
LoadAcceptEx();
|
|
}
|
|
}
|
|
|
|
|
|
bool ListenSocket::IsClosed() {
|
|
return IsClosing() && !HasPendingAccept();
|
|
}
|
|
|
|
|
|
int Handle::Available() {
|
|
ScopedLock lock(this);
|
|
if (data_ready_ == NULL) return 0;
|
|
ASSERT(!data_ready_->IsEmpty());
|
|
return data_ready_->GetRemainingLength();
|
|
}
|
|
|
|
|
|
int Handle::Read(void* buffer, int num_bytes) {
|
|
ScopedLock lock(this);
|
|
if (data_ready_ == NULL) return 0;
|
|
num_bytes = data_ready_->Read(buffer, num_bytes);
|
|
if (data_ready_->IsEmpty()) {
|
|
OverlappedBuffer::DisposeBuffer(data_ready_);
|
|
data_ready_ = NULL;
|
|
}
|
|
return num_bytes;
|
|
}
|
|
|
|
|
|
static unsigned int __stdcall WriteFileThread(void* args) {
|
|
Handle* handle = reinterpret_cast<Handle*>(args);
|
|
handle->WriteSyncCompleteAsync();
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Handle::WriteSyncCompleteAsync() {
|
|
ASSERT(pending_write_ != NULL);
|
|
|
|
DWORD bytes_written = -1;
|
|
BOOL ok = WriteFile(handle_,
|
|
pending_write_->GetBufferStart(),
|
|
pending_write_->GetBufferSize(),
|
|
&bytes_written,
|
|
NULL);
|
|
if (!ok) {
|
|
if (GetLastError() != ERROR_BROKEN_PIPE) {
|
|
Log::PrintErr("WriteFile failed %d\n", GetLastError());
|
|
}
|
|
bytes_written = 0;
|
|
}
|
|
thread_wrote_ += bytes_written;
|
|
OVERLAPPED* overlapped = pending_write_->GetCleanOverlapped();
|
|
ok = PostQueuedCompletionStatus(event_handler_->completion_port(),
|
|
bytes_written,
|
|
reinterpret_cast<ULONG_PTR>(this),
|
|
overlapped);
|
|
if (!ok) {
|
|
FATAL("PostQueuedCompletionStatus failed");
|
|
}
|
|
}
|
|
|
|
|
|
int Handle::Write(const void* buffer, int num_bytes) {
|
|
ScopedLock lock(this);
|
|
if (pending_write_ != NULL) return 0;
|
|
if (num_bytes > kBufferSize) num_bytes = kBufferSize;
|
|
if (SupportsOverlappedIO()) {
|
|
if (completion_port_ == INVALID_HANDLE_VALUE) return 0;
|
|
pending_write_ = OverlappedBuffer::AllocateWriteBuffer(num_bytes);
|
|
pending_write_->Write(buffer, num_bytes);
|
|
if (!IssueWrite()) return -1;
|
|
return num_bytes;
|
|
} else {
|
|
// In the case of stdout and stderr, OverlappedIO is not supported.
|
|
// Here we'll instead spawn a new thread for each write, 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.
|
|
// TODO(ajohnsen): Don't spawn a new thread per write. Issue 13541.
|
|
if (thread_wrote_ > 0) {
|
|
if (num_bytes > thread_wrote_) num_bytes = thread_wrote_;
|
|
thread_wrote_ -= num_bytes;
|
|
return num_bytes;
|
|
}
|
|
pending_write_ = OverlappedBuffer::AllocateWriteBuffer(num_bytes);
|
|
pending_write_->Write(buffer, num_bytes);
|
|
// Completing asynchronously through thread.
|
|
uint32_t tid;
|
|
uintptr_t thread_handle =
|
|
_beginthreadex(NULL, 32 * 1024, WriteFileThread, this, 0, &tid);
|
|
if (thread_handle == -1) {
|
|
FATAL("Failed to start write file thread");
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
bool ClientSocket::LoadDisconnectEx() {
|
|
// Load the DisconnectEx function into memory using WSAIoctl.
|
|
GUID guid_disconnect_ex = WSAID_DISCONNECTEX;
|
|
DWORD bytes;
|
|
int status = WSAIoctl(socket(),
|
|
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
|
&guid_disconnect_ex,
|
|
sizeof(guid_disconnect_ex),
|
|
&DisconnectEx_,
|
|
sizeof(DisconnectEx_),
|
|
&bytes,
|
|
NULL,
|
|
NULL);
|
|
if (status == SOCKET_ERROR) {
|
|
Log::PrintErr("Error WSAIoctl failed: %d\n", WSAGetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void ClientSocket::Shutdown(int how) {
|
|
int rc = shutdown(socket(), how);
|
|
if (how == SD_RECEIVE) MarkClosedRead();
|
|
if (how == SD_SEND) MarkClosedWrite();
|
|
if (how == SD_BOTH) {
|
|
MarkClosedRead();
|
|
MarkClosedWrite();
|
|
}
|
|
}
|
|
|
|
|
|
void ClientSocket::DoClose() {
|
|
// Always do a shutdown before initiating a disconnect.
|
|
shutdown(socket(), SD_BOTH);
|
|
IssueDisconnect();
|
|
}
|
|
|
|
|
|
bool ClientSocket::IssueRead() {
|
|
ScopedLock lock(this);
|
|
ASSERT(completion_port_ != INVALID_HANDLE_VALUE);
|
|
ASSERT(pending_read_ == NULL);
|
|
|
|
OverlappedBuffer* buffer = OverlappedBuffer::AllocateReadBuffer(1024);
|
|
|
|
DWORD flags;
|
|
flags = 0;
|
|
int rc = WSARecv(socket(),
|
|
buffer->GetWASBUF(),
|
|
1,
|
|
NULL,
|
|
&flags,
|
|
buffer->GetCleanOverlapped(),
|
|
NULL);
|
|
if (rc == NO_ERROR || WSAGetLastError() == WSA_IO_PENDING) {
|
|
pending_read_ = buffer;
|
|
return true;
|
|
}
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
pending_read_ = NULL;
|
|
HandleIssueError();
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ClientSocket::IssueWrite() {
|
|
ScopedLock lock(this);
|
|
ASSERT(completion_port_ != INVALID_HANDLE_VALUE);
|
|
ASSERT(pending_write_ != NULL);
|
|
ASSERT(pending_write_->operation() == OverlappedBuffer::kWrite);
|
|
|
|
int rc = WSASend(socket(),
|
|
pending_write_->GetWASBUF(),
|
|
1,
|
|
NULL,
|
|
0,
|
|
pending_write_->GetCleanOverlapped(),
|
|
NULL);
|
|
if (rc == NO_ERROR || WSAGetLastError() == WSA_IO_PENDING) {
|
|
return true;
|
|
}
|
|
OverlappedBuffer::DisposeBuffer(pending_write_);
|
|
pending_write_ = NULL;
|
|
HandleIssueError();
|
|
return false;
|
|
}
|
|
|
|
|
|
void ClientSocket::IssueDisconnect() {
|
|
Dart_Port p = port();
|
|
OverlappedBuffer* buffer = OverlappedBuffer::AllocateDisconnectBuffer();
|
|
BOOL ok = DisconnectEx_(
|
|
socket(), buffer->GetCleanOverlapped(), TF_REUSE_SOCKET, 0);
|
|
if (!ok && WSAGetLastError() != WSA_IO_PENDING) {
|
|
DisconnectComplete(buffer);
|
|
}
|
|
if (p != ILLEGAL_PORT) DartUtils::PostInt32(p, 1 << kDestroyedEvent);
|
|
}
|
|
|
|
|
|
void ClientSocket::DisconnectComplete(OverlappedBuffer* buffer) {
|
|
OverlappedBuffer::DisposeBuffer(buffer);
|
|
closesocket(socket());
|
|
if (data_ready_ != NULL) {
|
|
OverlappedBuffer::DisposeBuffer(data_ready_);
|
|
}
|
|
// When disconnect is complete get rid of the object.
|
|
delete this;
|
|
}
|
|
|
|
|
|
void ClientSocket::EnsureInitialized(
|
|
EventHandlerImplementation* event_handler) {
|
|
ScopedLock lock(this);
|
|
if (completion_port_ == INVALID_HANDLE_VALUE) {
|
|
ASSERT(event_handler_ == NULL);
|
|
event_handler_ = event_handler;
|
|
CreateCompletionPort(event_handler_->completion_port());
|
|
}
|
|
}
|
|
|
|
|
|
bool ClientSocket::IsClosed() {
|
|
return false;
|
|
}
|
|
|
|
|
|
static void DeleteIfClosed(Handle* handle) {
|
|
if (handle->IsClosed()) {
|
|
Dart_Port port = handle->port();
|
|
delete handle;
|
|
DartUtils::PostInt32(port, 1 << kDestroyedEvent);
|
|
}
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleInterrupt(InterruptMessage* msg) {
|
|
if (msg->id == kTimeoutId) {
|
|
// 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 {
|
|
Handle* handle = reinterpret_cast<Handle*>(msg->id);
|
|
ASSERT(handle != NULL);
|
|
if (handle->is_listen_socket()) {
|
|
ListenSocket* listen_socket =
|
|
reinterpret_cast<ListenSocket*>(handle);
|
|
listen_socket->EnsureInitialized(this);
|
|
listen_socket->SetPortAndMask(msg->dart_port, msg->data);
|
|
|
|
Handle::ScopedLock lock(listen_socket);
|
|
|
|
// If incomming connections are requested make sure to post already
|
|
// accepted connections.
|
|
if ((msg->data & (1 << kInEvent)) != 0) {
|
|
if (listen_socket->CanAccept()) {
|
|
int event_mask = (1 << kInEvent);
|
|
handle->set_mask(handle->mask() & ~event_mask);
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
}
|
|
// Always keep 5 outstanding accepts going, to enhance performance.
|
|
while (listen_socket->pending_accept_count() < 5) {
|
|
listen_socket->IssueAccept();
|
|
}
|
|
}
|
|
|
|
if ((msg->data & (1 << kCloseCommand)) != 0) {
|
|
listen_socket->Close();
|
|
}
|
|
} else {
|
|
handle->EnsureInitialized(this);
|
|
|
|
Handle::ScopedLock lock(handle);
|
|
|
|
if (!handle->IsError()) {
|
|
if ((msg->data & ((1 << kInEvent) | (1 << kOutEvent))) != 0) {
|
|
// Only set mask if we turned on kInEvent or kOutEvent.
|
|
handle->SetPortAndMask(msg->dart_port, msg->data);
|
|
|
|
// If in events (data available events) have been requested, and data
|
|
// is available, post an in event immediately. Otherwise make sure
|
|
// that a pending read is issued, unless the socket is already closed
|
|
// for read.
|
|
if ((msg->data & (1 << kInEvent)) != 0) {
|
|
if (handle->Available() > 0) {
|
|
int event_mask = (1 << kInEvent);
|
|
handle->set_mask(handle->mask() & ~event_mask);
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
} else if (handle->IsClosedRead()) {
|
|
int event_mask = (1 << kCloseEvent);
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
} else if (!handle->HasPendingRead()) {
|
|
handle->IssueRead();
|
|
}
|
|
}
|
|
|
|
// If out events (can write events) have been requested, and there
|
|
// are no pending writes, post an out event immediately.
|
|
if ((msg->data & (1 << kOutEvent)) != 0) {
|
|
if (!handle->HasPendingWrite()) {
|
|
int event_mask = (1 << kOutEvent);
|
|
handle->set_mask(handle->mask() & ~event_mask);
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (handle->is_client_socket()) {
|
|
ClientSocket* client_socket = reinterpret_cast<ClientSocket*>(handle);
|
|
if ((msg->data & (1 << kShutdownReadCommand)) != 0) {
|
|
client_socket->Shutdown(SD_RECEIVE);
|
|
}
|
|
|
|
if ((msg->data & (1 << kShutdownWriteCommand)) != 0) {
|
|
client_socket->Shutdown(SD_SEND);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((msg->data & (1 << kCloseCommand)) != 0) {
|
|
handle->SetPortAndMask(msg->dart_port, msg->data);
|
|
handle->Close();
|
|
}
|
|
}
|
|
DeleteIfClosed(handle);
|
|
}
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleAccept(ListenSocket* listen_socket,
|
|
OverlappedBuffer* buffer) {
|
|
listen_socket->AcceptComplete(buffer, completion_port_);
|
|
|
|
if (!listen_socket->IsClosing()) {
|
|
int event_mask = 1 << kInEvent;
|
|
if ((listen_socket->mask() & event_mask) != 0) {
|
|
DartUtils::PostInt32(listen_socket->port(), event_mask);
|
|
}
|
|
}
|
|
|
|
DeleteIfClosed(listen_socket);
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleClosed(Handle* handle) {
|
|
if (!handle->IsClosing()) {
|
|
int event_mask = 1 << kCloseEvent;
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
}
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleError(Handle* handle) {
|
|
handle->set_last_error(WSAGetLastError());
|
|
handle->MarkError();
|
|
if (!handle->IsClosing()) {
|
|
int event_mask = 1 << kErrorEvent;
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
}
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleRead(Handle* handle,
|
|
int bytes,
|
|
OverlappedBuffer* buffer) {
|
|
buffer->set_data_length(bytes);
|
|
handle->ReadComplete(buffer);
|
|
if (bytes > 0) {
|
|
if (!handle->IsClosing()) {
|
|
int event_mask = 1 << kInEvent;
|
|
if ((handle->mask() & event_mask) != 0) {
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
}
|
|
}
|
|
} else {
|
|
handle->MarkClosedRead();
|
|
if (bytes == 0) {
|
|
HandleClosed(handle);
|
|
} else {
|
|
HandleError(handle);
|
|
}
|
|
}
|
|
|
|
DeleteIfClosed(handle);
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleWrite(Handle* handle,
|
|
int bytes,
|
|
OverlappedBuffer* buffer) {
|
|
handle->WriteComplete(buffer);
|
|
|
|
if (bytes > 0) {
|
|
if (!handle->IsError() && !handle->IsClosing()) {
|
|
int event_mask = 1 << kOutEvent;
|
|
if ((handle->mask() & event_mask) != 0) {
|
|
DartUtils::PostInt32(handle->port(), event_mask);
|
|
}
|
|
}
|
|
} else if (bytes == 0) {
|
|
HandleClosed(handle);
|
|
} else {
|
|
HandleError(handle);
|
|
}
|
|
|
|
DeleteIfClosed(handle);
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleDisconnect(
|
|
ClientSocket* client_socket,
|
|
int bytes,
|
|
OverlappedBuffer* buffer) {
|
|
client_socket->DisconnectComplete(buffer);
|
|
}
|
|
|
|
void EventHandlerImplementation::HandleTimeout() {
|
|
if (!timeout_queue_.HasTimeout()) return;
|
|
DartUtils::PostNull(timeout_queue_.CurrentPort());
|
|
timeout_queue_.RemoveCurrent();
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::HandleIOCompletion(DWORD bytes,
|
|
ULONG_PTR key,
|
|
OVERLAPPED* overlapped) {
|
|
OverlappedBuffer* buffer = OverlappedBuffer::GetFromOverlapped(overlapped);
|
|
switch (buffer->operation()) {
|
|
case OverlappedBuffer::kAccept: {
|
|
ListenSocket* listen_socket = reinterpret_cast<ListenSocket*>(key);
|
|
HandleAccept(listen_socket, buffer);
|
|
break;
|
|
}
|
|
case OverlappedBuffer::kRead: {
|
|
Handle* handle = reinterpret_cast<Handle*>(key);
|
|
HandleRead(handle, bytes, buffer);
|
|
break;
|
|
}
|
|
case OverlappedBuffer::kWrite: {
|
|
Handle* handle = reinterpret_cast<Handle*>(key);
|
|
HandleWrite(handle, bytes, buffer);
|
|
break;
|
|
}
|
|
case OverlappedBuffer::kDisconnect: {
|
|
ClientSocket* client_socket = reinterpret_cast<ClientSocket*>(key);
|
|
HandleDisconnect(client_socket, bytes, buffer);
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
|
|
EventHandlerImplementation::EventHandlerImplementation() {
|
|
intptr_t result;
|
|
completion_port_ =
|
|
CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1);
|
|
if (completion_port_ == NULL) {
|
|
FATAL("Completion port creation failed");
|
|
}
|
|
shutdown_ = false;
|
|
}
|
|
|
|
|
|
EventHandlerImplementation::~EventHandlerImplementation() {
|
|
CloseHandle(completion_port_);
|
|
}
|
|
|
|
|
|
int64_t EventHandlerImplementation::GetTimeout() {
|
|
if (!timeout_queue_.HasTimeout()) {
|
|
return kInfinityTimeout;
|
|
}
|
|
int64_t millis = timeout_queue_.CurrentTimeout() -
|
|
TimerUtils::GetCurrentTimeMilliseconds();
|
|
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<OVERLAPPED*>(msg));
|
|
if (!ok) {
|
|
FATAL("PostQueuedCompletionStatus failed");
|
|
}
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::EventHandlerEntry(uword args) {
|
|
EventHandler* handler = reinterpret_cast<EventHandler*>(args);
|
|
EventHandlerImplementation* handler_impl = &handler->delegate_;
|
|
ASSERT(handler_impl != NULL);
|
|
while (!handler_impl->shutdown_) {
|
|
DWORD bytes;
|
|
ULONG_PTR key;
|
|
OVERLAPPED* overlapped;
|
|
int64_t millis = handler_impl->GetTimeout();
|
|
ASSERT(millis == kInfinityTimeout || millis >= 0);
|
|
if (millis > kMaxInt32) millis = kMaxInt32;
|
|
ASSERT(sizeof(int32_t) == sizeof(DWORD));
|
|
BOOL ok = GetQueuedCompletionStatus(handler_impl->completion_port(),
|
|
&bytes,
|
|
&key,
|
|
&overlapped,
|
|
static_cast<DWORD>(millis));
|
|
if (!ok && overlapped == NULL) {
|
|
if (GetLastError() == ERROR_ABANDONED_WAIT_0) {
|
|
// The completion port should never be closed.
|
|
Log::Print("Completion port closed\n");
|
|
UNREACHABLE();
|
|
} else {
|
|
// Timeout is signalled by false result and NULL in overlapped.
|
|
handler_impl->HandleTimeout();
|
|
}
|
|
} else 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);
|
|
handler_impl->HandleIOCompletion(bytes, key, overlapped);
|
|
} else {
|
|
ASSERT(bytes == 0);
|
|
handler_impl->HandleIOCompletion(-1, key, overlapped);
|
|
}
|
|
} else if (key == NULL) {
|
|
// A key of NULL signals an interrupt message.
|
|
InterruptMessage* msg = reinterpret_cast<InterruptMessage*>(overlapped);
|
|
handler_impl->HandleInterrupt(msg);
|
|
delete msg;
|
|
} else {
|
|
handler_impl->HandleIOCompletion(bytes, key, overlapped);
|
|
}
|
|
}
|
|
delete handler;
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::Start(EventHandler* handler) {
|
|
int result = dart::Thread::Start(EventHandlerEntry,
|
|
reinterpret_cast<uword>(handler));
|
|
if (result != 0) {
|
|
FATAL1("Failed to start event handler thread %d", result);
|
|
}
|
|
|
|
// Initialize Winsock32
|
|
if (!Socket::Initialize()) {
|
|
FATAL("Failed to initialized Windows sockets");
|
|
}
|
|
}
|
|
|
|
|
|
void EventHandlerImplementation::Shutdown() {
|
|
SendData(kShutdownId, 0, 0);
|
|
}
|
|
|
|
} // namespace bin
|
|
} // namespace dart
|
|
|
|
#endif // defined(TARGET_OS_WINDOWS)
|