blob: 625a097b510722a4ef57b573ddf5bda4d0c5fdf0 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/profiling/memlog_sender_pipe.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/profiling/memlog_stream.h"
namespace profiling {
namespace {
// The documentation for ::WriteFileEx indicates that the last parameter is an
// OVERLAPPED*. But OVERLAPPED has no member to hold a void* context for
// MemlogSenderPipe, and without that, the callback must only use global
// variables. This is problematic. The example
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365601(v=vs.85).aspx
// instead uses a struct whose first member is an OVERLAPPED object, and passes
// a struct pointer to ::WriteFileEx.
struct OverlappedWriteContext {
OverlappedWriteContext()
: waiting_for_write(true), bytes_written(0), error(ERROR_SUCCESS) {
memset(&overlap, 0, sizeof(overlap));
overlap.Offset = 0xFFFFFFFF;
overlap.OffsetHigh = 0xFFFFFFFF;
}
// This must always be the first member.
OVERLAPPED overlap;
bool waiting_for_write;
DWORD bytes_written;
DWORD error;
};
static_assert(offsetof(OverlappedWriteContext, overlap) == 0,
"overlap must always be the first member.");
// A global function called by ::WriteFileEx when the write has finished, or
// errored.
void WINAPI AsyncWriteFinishedGlobal(DWORD error,
DWORD bytes_written,
LPOVERLAPPED overlap) {
OverlappedWriteContext* context =
reinterpret_cast<OverlappedWriteContext*>(overlap);
context->waiting_for_write = false;
context->bytes_written = bytes_written;
context->error = error;
}
} // namespace
MemlogSenderPipe::PipePair::PipePair() {
std::wstring pipe_name = base::StringPrintf(
L"\\\\.\\pipe\\profiling.%u.%u.%I64u", GetCurrentProcessId(),
GetCurrentThreadId(), base::RandUint64());
HANDLE handle = CreateNamedPipe(
pipe_name.c_str(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
1, // Max instances.
kPipeSize, // Out buffer size.
kPipeSize, // In buffer size.
5000, // Timeout in milliseconds for connecting the receiving pipe. Has
// nothing to do with Send() timeout.
nullptr);
PCHECK(handle != INVALID_HANDLE_VALUE);
receiver_.reset(mojo::edk::PlatformHandle(handle));
// Allow the handle to be inherited by child processes.
SECURITY_ATTRIBUTES security_attributes;
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.lpSecurityDescriptor = nullptr;
security_attributes.bInheritHandle = TRUE;
handle = CreateFile(
pipe_name.c_str(), GENERIC_WRITE,
0, // No sharing.
&security_attributes, OPEN_EXISTING,
SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED,
nullptr);
PCHECK(handle != INVALID_HANDLE_VALUE);
sender_.reset(mojo::edk::PlatformHandle(handle));
// Since a client has connected, ConnectNamedPipe() should return zero and
// GetLastError() should return ERROR_PIPE_CONNECTED.
BOOL result = ConnectNamedPipe(receiver_.get().handle, nullptr);
DWORD error = GetLastError();
CHECK((result == 0) && (error == ERROR_PIPE_CONNECTED));
}
MemlogSenderPipe::PipePair::PipePair(PipePair&& other) = default;
MemlogSenderPipe::MemlogSenderPipe(base::ScopedPlatformFile file)
: file_(std::move(file)) {}
MemlogSenderPipe::~MemlogSenderPipe() {
}
MemlogSenderPipe::Result MemlogSenderPipe::Send(const void* data,
size_t size,
int timeout_ms) {
// The pipe is nonblocking. However, to ensure that messages on different
// threads are serialized and in order:
// 1) We grab a global lock.
// 2) We attempt to synchronously write, but with a timeout. On timeout
// or error, the MemlogSenderPipe is shut down.
base::AutoLock lock(lock_);
// This can happen if Close() was called on another thread, while this thread
// was already waiting to call MemlogSenderPipe::Send().
if (!file_.IsValid())
return Result::kError;
// Queue an asynchronous write.
OverlappedWriteContext context;
// It's safe to use a raw pointer to |context|, since it will stay on the
// stack until ::SleepEx returns, at which point either the callback has
// finished, or will be cancelled.
BOOL write_result = ::WriteFileEx(file_.Get(), data, static_cast<DWORD>(size),
&context.overlap, AsyncWriteFinishedGlobal);
// Check for errors.
if (!write_result)
return Result::kError;
// The documentation for ::WriteFileEx
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365748(v=vs.85).aspx
// claims that we need to check GetLastError() even on success. This is
// incorrect. GetLastError() returns the error from the previous Windows
// library call.
while (true) {
// The return code of ::SleepEx has multiple semantics. Do not replace this
// with PlatformThread::Sleep.
DWORD sleep_result = ::SleepEx(timeout_ms, TRUE);
// Timeout reached.
if (sleep_result == 0) {
BOOL r = ::CancelIo(file_.Get());
DCHECK_NE(0, r);
DWORD r2 = ::WaitForSingleObject(file_.Get(), INFINITE);
DCHECK_EQ(WAIT_OBJECT_0, r2);
return Result::kTimeout;
}
// Unexpected error.
if (sleep_result != WAIT_IO_COMPLETION)
return Result::kError;
// In the very rare case where this function returns from the completion of
// another async IO handler, just repeat the sleep duration. This allows us
// to avoid a call to base::TimeTicks::Now() in the common case.
if (context.waiting_for_write)
continue;
if (context.error != ERROR_SUCCESS)
return Result::kError;
// Partial writes should not be possible.
DCHECK_EQ(context.bytes_written, size);
return Result::kSuccess;
}
}
void MemlogSenderPipe::Close() {
base::AutoLock lock(lock_);
file_.Close();
}
} // namespace profiling