blob: 24f733a01428b946f246b4efa1e5f9567d0e15a7 [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/net_log.h"
#include <windows.h>
#include <algorithm>
#include <deque>
#include <functional>
#include <map>
#include <string>
#include "starboard/atomic.h"
#include "starboard/common/log.h"
#include "starboard/common/mutex.h"
#include "starboard/common/semaphore.h"
#include "starboard/common/socket.h"
#include "starboard/common/string.h"
#include "starboard/common/thread.h"
#include "starboard/once.h"
#include "starboard/socket_waiter.h"
#ifndef NET_LOG_PORT
// Default port was generated from a random number generator.
#define NET_LOG_PORT 49353
#endif
// Controls whether using IPv4 or IPv6.
#ifndef NET_LOG_IP_VERSION
#define NET_LOG_IP_VERSION kSbSocketAddressTypeIpv4
#endif
// Default Socket write buffer is 100k.
#ifndef NET_LOG_SOCKET_BUFFER_SIZE
#define NET_LOG_SOCKET_BUFFER_SIZE (1024 * 100)
#endif
// Default in memory write buffer is 1mb.
#ifndef NET_LOG_MAX_IN_MEMORY_BUFFER
#define NET_LOG_MAX_IN_MEMORY_BUFFER (1024 * 1024)
#endif
// Default block to send to the socket is 1k.
#ifndef NET_LOG_SOCKET_SEND_SIZE
#define NET_LOG_SOCKET_SEND_SIZE 1024
#endif
namespace starboard {
namespace shared {
namespace starboard {
namespace {
using RunFunction = std::function<void(Semaphore*)>;
class FunctionThread : public Thread {
public:
static scoped_ptr<Thread> Create(const std::string& thread_name,
RunFunction run_function) {
scoped_ptr<Thread> out(new FunctionThread(thread_name, run_function));
return out.Pass();
}
FunctionThread(const std::string& name, RunFunction run_function)
: Thread(name), run_function_(run_function) {}
void Run() override { run_function_(join_sema()); }
private:
RunFunction run_function_;
};
std::string ToString(SbSocketError error) {
switch (error) {
case kSbSocketOk: {
return "kSbSocketOk";
}
case kSbSocketPending: {
return "kSbSocketErrorConnectionReset";
}
case kSbSocketErrorFailed: {
return "kSbSocketErrorFailed";
}
#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT) || SB_API_VERSION >= 9
case kSbSocketErrorConnectionReset: {
return "kSbSocketErrorConnectionReset";
}
#endif // SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT) ||
// SB_API_VERSION >= 9
}
SB_NOTREACHED() << "Unexpected case " << error;
std::stringstream ss;
ss << "Unknown-" << error;
return ss.str();
}
scoped_ptr<Socket> CreateListenSocket() {
scoped_ptr<Socket> socket(
new Socket(NET_LOG_IP_VERSION, kSbSocketProtocolTcp));
socket->SetReuseAddress(true);
SbSocketAddress sock_addr;
// Ip address will be set to 0.0.0.0 so that it will bind to all sockets.
SbMemorySet(&sock_addr, 0, sizeof(SbSocketAddress));
sock_addr.type = NET_LOG_IP_VERSION;
sock_addr.port = NET_LOG_PORT;
SbSocketError sock_err = socket->Bind(&sock_addr);
const char kErrFmt[] = "Socket error while attempting to bind, error = %d\n";
// Can't use SB_LOG_IF() because SB_LOG_IF() might have triggered this call,
// and therefore would triggered reentrant behavior and then deadlock.
// SbLogRawFormat() is assumed to be safe to call. Note that NetLogWrite()
// ignores reentrant calls.
if (sock_err != kSbSocketOk) {
SbLogRawFormatF(kErrFmt, sock_err);
}
sock_err = socket->Listen();
if (sock_err != kSbSocketOk) {
SbLogRawFormatF(kErrFmt, sock_err);
}
return socket.Pass();
}
class BufferedSocketWriter {
public:
BufferedSocketWriter(int in_memory_buffer_size, int chunk_size)
: max_memory_buffer_size_(in_memory_buffer_size),
chunk_size_(chunk_size) {}
void Append(const char* data, size_t data_n) {
bool overflow = false;
log_mutex_.Acquire();
for (const char* curr = data; curr != data + data_n; ++curr) {
log_.push_back(*curr);
if (log_.size() > max_memory_buffer_size_) {
overflow = true;
log_.pop_front();
}
}
log_mutex_.Release();
if (overflow) {
// Can't use SB_LOG_IF() because SB_LOG_IF() might have triggered this
// call, and therefore would triggered reentrant behavior and then
// deadlock. SbLogRawFormat() is assumed to be safe to call. Note that
// NetLogWrite() ignores reentrant calls.
SbLogRaw("Net log dropped buffer data.");
}
}
void WaitUntilWritableOrConnectionReset(SbSocket sock) {
SbSocketWaiter waiter = SbSocketWaiterCreate();
struct F {
static void WakeUp(SbSocketWaiter waiter, SbSocket, void*, int) {
SbSocketWaiterWakeUp(waiter);
}
};
SbSocketWaiterAdd(waiter, sock, NULL, &F::WakeUp,
kSbSocketWaiterInterestWrite,
false); // false means one shot.
SbSocketWaiterWait(waiter);
SbSocketWaiterRemove(waiter, sock);
SbSocketWaiterDestroy(waiter);
}
bool IsConnectionReset(SbSocketError err) {
#if SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT) || SB_API_VERSION >= 9
return err == kSbSocketErrorConnectionReset;
#else
return false;
#endif // SB_HAS(SOCKET_ERROR_CONNECTION_RESET_SUPPORT) ||
// SB_API_VERSION >= 9
}
// Will flush data through to the dest_socket. Returns |true| if
// flushed, else connection was dropped or an error occured.
bool Flush(SbSocket dest_socket) {
std::string curr_write_block;
while (TransferData(chunk_size_, &curr_write_block)) {
while (!curr_write_block.empty()) {
int bytes_to_write = static_cast<int>(curr_write_block.size());
int result = SbSocketSendTo(dest_socket, curr_write_block.c_str(),
bytes_to_write, NULL);
if (result < 0) {
SbSocketError err = SbSocketGetLastError(dest_socket);
SbSocketClearLastError(dest_socket);
if (err == kSbSocketPending) {
blocked_counts_.increment();
WaitUntilWritableOrConnectionReset(dest_socket);
continue;
} else if (IsConnectionReset(err)) {
return false;
} else {
SB_LOG(ERROR) << "An error happened while writing to socket: "
<< ToString(err);
return false;
}
break;
} else if (result == 0) {
// Socket has closed.
return false;
} else {
// Expected condition. Partial or full write was successful.
size_t bytes_written = static_cast<size_t>(result);
SB_DCHECK(bytes_written <= bytes_to_write);
curr_write_block.erase(0, bytes_written);
}
}
}
return true;
}
int32_t blocked_counts() const { return blocked_counts_.load(); }
private:
bool TransferData(size_t max_size, std::string* destination) {
ScopedLock lock_log(log_mutex_);
size_t log_size = log_.size();
if (log_size == 0) {
return false;
}
size_t size = std::min<size_t>(max_size, log_size);
std::deque<char>::iterator begin_it = log_.begin();
std::deque<char>::iterator end_it = begin_it;
std::advance(end_it, size);
destination->assign(begin_it, end_it);
log_.erase(begin_it, end_it);
return true;
}
void PrependData(const std::string& curr_write_block) {
ScopedLock lock_log(log_mutex_);
log_.insert(log_.begin(), curr_write_block.begin(), curr_write_block.end());
}
int max_memory_buffer_size_;
int chunk_size_;
Mutex log_mutex_;
std::deque<char> log_;
atomic_int32_t blocked_counts_;
};
// This class will listen to the provided socket for a client
// connection. When a client connection is established, a
// callback will be invoked.
class SocketListener {
public:
typedef std::function<void(scoped_ptr<Socket>)> Callback;
SocketListener(Socket* listen_socket, Callback cb)
: listen_socket_(listen_socket), callback_(cb) {
auto run_cb = [this](Semaphore* sema) { this->Run(sema); };
thread_ = FunctionThread::Create("NetLogSocketListener", run_cb);
thread_->Start();
}
~SocketListener() { thread_->Join(); }
private:
void Run(Semaphore* joined_sema) {
while (!joined_sema->TakeWait(100 * kSbTimeMillisecond)) {
scoped_ptr<Socket> client_connection(listen_socket_->Accept());
if (client_connection) {
callback_(client_connection.Pass());
break;
}
}
}
Socket* listen_socket_;
Callback callback_;
scoped_ptr<Thread> thread_;
};
class NetLogServer {
public:
static NetLogServer* Instance();
NetLogServer()
: buffered_socket_writer_(NET_LOG_MAX_IN_MEMORY_BUFFER,
NET_LOG_SOCKET_SEND_SIZE) {
ScopedLock lock(socket_mutex_);
listen_socket_ = CreateListenSocket();
ListenForClient();
writer_thread_ = FunctionThread::Create(
"NetLogSocketWriter",
[this](Semaphore* sema) { this->WriterTask(sema); });
writer_thread_->Start();
}
~NetLogServer() {
SB_NOTREACHED(); // Should never reach here because of singleton use.
Close();
}
void ListenForClient() {
SocketListener::Callback cb =
std::bind(&NetLogServer::OnClientConnect, this, std::placeholders::_1);
socket_listener_.reset();
socket_listener_.reset(new SocketListener(listen_socket_.get(), cb));
}
void OnClientConnect(scoped_ptr<Socket> client_socket) {
ScopedLock lock(socket_mutex_);
client_socket_ = client_socket.Pass();
client_socket_->SetSendBufferSize(NET_LOG_SOCKET_BUFFER_SIZE);
client_socket_->SetTcpKeepAlive(true, kSbTimeSecond);
}
// Has a client ever connected?
bool HasClientConnected() {
ScopedLock lock(socket_mutex_);
return client_socket_;
}
void OnLog(const char* msg) {
buffered_socket_writer_.Append(msg, SbStringGetLength(msg));
writer_thread_sema_.Put();
}
void Close() {
socket_listener_.reset();
if (writer_thread_) {
is_joined_.store(true);
writer_thread_sema_.Put();
writer_thread_->Join();
writer_thread_.reset(nullptr);
Flush(); // One last flush to the socket.
}
ScopedLock lock(socket_mutex_);
client_socket_.reset();
listen_socket_.reset();
}
// Return |true| if the data was written out to a connected socket,
// else |false| if
// 1. There was no connected client.
// 2. The connection was dropped.
// 3. Some other connection error happened.
bool Flush() {
ScopedLock lock(socket_mutex_);
if (!client_socket_) {
return false;
}
return buffered_socket_writer_.Flush(client_socket_->socket());
}
private:
void WriterTask(Semaphore* joined_sema) {
while (true) {
writer_thread_sema_.Take();
if (HasClientConnected()) {
if (!Flush()) {
break; // Connection dropped.
}
}
if (is_joined_.load()) {
break;
}
}
}
scoped_ptr<Socket> listen_socket_;
scoped_ptr<Socket> client_socket_;
Mutex socket_mutex_;
scoped_ptr<SocketListener> socket_listener_;
scoped_ptr<Thread> writer_thread_;
Semaphore writer_thread_sema_;
atomic_bool is_joined_;
BufferedSocketWriter buffered_socket_writer_;
};
class ThreadLocalBoolean {
public:
ThreadLocalBoolean() : slot_(SbThreadCreateLocalKey(NULL)) {}
~ThreadLocalBoolean() { SbThreadDestroyLocalKey(slot_); }
void Set(bool val) {
void* ptr = val ? reinterpret_cast<void*>(0x1) : nullptr;
SbThreadSetLocalValue(slot_, ptr);
}
bool Get() const {
void* ptr = SbThreadGetLocalValue(slot_);
return ptr != nullptr;
}
private:
SbThreadLocalKey slot_;
ThreadLocalBoolean(const ThreadLocalBoolean&) = delete;
void operator=(const ThreadLocalBoolean&) = delete;
};
SB_ONCE_INITIALIZE_FUNCTION(NetLogServer, NetLogServer::Instance);
SB_ONCE_INITIALIZE_FUNCTION(ThreadLocalBoolean, ScopeGuardTLB);
// Prevents re-entrant behavior for sending logs. This is useful if/when
// sub-routines will invoke logging on an error condition.
class ScopeGuard {
public:
ScopeGuard() : disabled_(false), tlb_(ScopeGuardTLB()) {
disabled_ = tlb_->Get();
tlb_->Set(true);
}
~ScopeGuard() { tlb_->Set(disabled_); }
bool IsEnabled() { return !disabled_; }
private:
bool disabled_;
ThreadLocalBoolean* tlb_;
};
} // namespace.
const char kNetLogCommandSwitchWait[] = "net_log_wait_for_connection";
void NetLogWaitForClientConnected(SbTime timeout) {
#if !SB_LOGGING_IS_OFFICIAL_BUILD
ScopeGuard guard;
if (guard.IsEnabled()) {
SbTimeMonotonic expire_time = (timeout >= 0) && (timeout < kSbTimeMax)
? SbTimeGetMonotonicNow() + timeout
: kSbTimeMax;
while (true) {
if (NetLogServer::Instance()->HasClientConnected()) {
break;
}
if (SbTimeGetMonotonicNow() > expire_time) {
break;
}
SbThreadSleep(kSbTimeMillisecond);
}
}
#endif
}
void NetLogWrite(const char* data) {
#if !SB_LOGGING_IS_OFFICIAL_BUILD
ScopeGuard guard;
if (guard.IsEnabled()) {
NetLogServer::Instance()->OnLog(data);
}
#endif
}
void NetLogFlush() {
#if !SB_LOGGING_IS_OFFICIAL_BUILD
ScopeGuard guard;
if (guard.IsEnabled()) {
NetLogServer::Instance()->Flush();
}
#endif
}
void NetLogFlushThenClose() {
#if !SB_LOGGING_IS_OFFICIAL_BUILD
ScopeGuard guard;
if (guard.IsEnabled()) {
NetLogServer* net_log = NetLogServer::Instance();
net_log->OnLog("Netlog is closing down\n");
net_log->Close();
}
#endif
}
} // namespace starboard
} // namespace shared
} // namespace starboard