blob: 7ab3a4b3ad3cb63f5f45ad06a0d3ea14b9955417 [file] [log] [blame]
// Copyright 2015 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/libevent/socket_waiter_internal.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <map>
#include <utility>
#include "starboard/common/log.h"
#include "starboard/shared/posix/handle_eintr.h"
#include "starboard/shared/posix/set_non_blocking_internal.h"
#include "starboard/shared/posix/socket_internal.h"
#include "starboard/shared/posix/time_internal.h"
#include "starboard/thread.h"
#include "third_party/libevent/event.h"
namespace sbposix = starboard::shared::posix;
namespace {
// We do this because it's our style to use explicitly-sized ints when not just
// using int, but libevent uses shorts explicitly in its interface.
SB_COMPILE_ASSERT(sizeof(int16_t) == sizeof(short), // NOLINT[runtime/int]
Short_is_not_int16);
SbSocketAddress GetIpv4Localhost() {
SbSocketAddress address = {0};
address.type = kSbSocketAddressTypeIpv4;
address.port = 0;
address.address[0] = 127;
address.address[3] = 1;
return address;
}
SbSocket AcceptBySpinning(SbSocket server_socket, SbTime timeout) {
SbTimeMonotonic start = SbTimeGetMonotonicNow();
while (true) {
SbSocket accepted_socket = SbSocketAccept(server_socket);
if (SbSocketIsValid(accepted_socket)) {
return accepted_socket;
}
// If we didn't get a socket, it should be pending.
SB_DCHECK(SbSocketGetLastError(server_socket) == kSbSocketPending);
// Check if we have passed our timeout.
if (SbTimeGetMonotonicNow() - start >= timeout) {
break;
}
// Just being polite.
SbThreadYield();
}
return kSbSocketInvalid;
}
#if !SB_HAS(PIPE)
void GetSocketPipe(SbSocket* client_socket, SbSocket* server_socket) {
int result;
SbSocketError sb_socket_result;
const SbTimeMonotonic kTimeout = kSbTimeSecond / 15;
SbSocketAddress address = GetIpv4Localhost();
// Setup a listening socket.
SbSocket listen_socket =
SbSocketCreate(kSbSocketAddressTypeIpv4, kSbSocketProtocolTcp);
SB_DCHECK(SbSocketIsValid(listen_socket));
result = SbSocketSetReuseAddress(listen_socket, true);
SB_DCHECK(result);
sb_socket_result = SbSocketBind(listen_socket, &address);
SB_DCHECK(sb_socket_result == kSbSocketOk);
sb_socket_result = SbSocketListen(listen_socket);
SB_DCHECK(sb_socket_result == kSbSocketOk);
// Update the address after a free port has been assigned.
SbSocketGetLocalAddress(listen_socket, &address);
// Create a new socket to connect to the listening socket.
*client_socket =
SbSocketCreate(kSbSocketAddressTypeIpv4, kSbSocketProtocolTcp);
SB_DCHECK(SbSocketIsValid(*client_socket));
// This connect will probably return pending, but we'll assume it will connect
// eventually.
sb_socket_result = SbSocketConnect(*client_socket, &address);
SB_DCHECK(sb_socket_result == kSbSocketOk ||
sb_socket_result == kSbSocketPending);
// Spin until the accept happens (or we get impatient).
*server_socket = AcceptBySpinning(listen_socket, kTimeout);
SB_DCHECK(SbSocketIsValid(*server_socket));
result = SbSocketDestroy(listen_socket);
SB_DCHECK(result);
}
#endif
} // namespace
SbSocketWaiterPrivate::SbSocketWaiterPrivate()
: thread_(SbThreadGetCurrent()),
base_(event_base_new()),
waiting_(false),
woken_up_(false) {
#if SB_HAS(PIPE)
int fds[2];
int result = pipe(fds);
SB_DCHECK(result == 0);
wakeup_read_fd_ = fds[0];
result = sbposix::SetNonBlocking(wakeup_read_fd_);
SB_DCHECK(result);
wakeup_write_fd_ = fds[1];
result = sbposix::SetNonBlocking(wakeup_write_fd_);
SB_DCHECK(result);
#else
GetSocketPipe(&client_socket_, &server_socket_);
// Set TCP_NODELAY on the server socket, so it immediately sends its tiny
// payload without waiting for more data.
SbSocketSetTcpNoDelay(server_socket_, true);
wakeup_read_fd_ = client_socket_->socket_fd;
wakeup_write_fd_ = server_socket_->socket_fd;
#endif
event_set(&wakeup_event_, wakeup_read_fd_, EV_READ | EV_PERSIST,
&SbSocketWaiterPrivate::LibeventWakeUpCallback, this);
event_base_set(base_, &wakeup_event_);
event_add(&wakeup_event_, NULL);
}
SbSocketWaiterPrivate::~SbSocketWaiterPrivate() {
WaiteesMap::iterator it = waitees_.begin();
while (it != waitees_.end()) {
Waitee* waitee = it->second;
++it; // Increment before removal.
Remove(waitee->socket);
}
event_del(&wakeup_event_);
event_base_free(base_);
#if SB_HAS(PIPE)
close(wakeup_read_fd_);
close(wakeup_write_fd_);
#else
SbSocketDestroy(server_socket_);
SbSocketDestroy(client_socket_);
#endif
}
bool SbSocketWaiterPrivate::Add(SbSocket socket,
void* context,
SbSocketWaiterCallback callback,
int interests,
bool persistent) {
SB_DCHECK(SbThreadIsCurrent(thread_));
if (!SbSocketIsValid(socket)) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Socket (" << socket << ") is invalid.";
return false;
}
if (!interests) {
SB_DLOG(ERROR) << __FUNCTION__ << ": No interests provided.";
return false;
}
// The policy is not to add a socket to a waiter if it is registered with
// another waiter.
// TODO: If anyone were to want to add a socket to a different waiter,
// it would probably be another thread, so doing this check without locking is
// probably wrong. But, it is also a pain, and, at this precise moment, socket
// access is all going to come from one I/O thread anyway, and there will only
// be one waiter.
if (SbSocketWaiterIsValid(socket->waiter)) {
if (socket->waiter == this) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Socket already has this waiter ("
<< this << ").";
} else {
SB_DLOG(ERROR) << __FUNCTION__ << ": Socket already has waiter ("
<< socket->waiter << ", this=" << this << ").";
}
return false;
}
Waitee* waitee =
new Waitee(this, socket, context, callback, interests, persistent);
AddWaitee(waitee);
int16_t events = 0;
if (interests & kSbSocketWaiterInterestRead) {
events |= EV_READ;
}
if (interests & kSbSocketWaiterInterestWrite) {
events |= EV_WRITE;
}
if (persistent) {
events |= EV_PERSIST;
}
event_set(&waitee->event, socket->socket_fd, events,
&SbSocketWaiterPrivate::LibeventSocketCallback, waitee);
event_base_set(base_, &waitee->event);
socket->waiter = this;
event_add(&waitee->event, NULL);
return true;
}
bool SbSocketWaiterPrivate::Remove(SbSocket socket) {
SB_DCHECK(SbThreadIsCurrent(thread_));
if (!SbSocketIsValid(socket)) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Socket (" << socket << ") is invalid.";
return false;
}
if (socket->waiter != this) {
SB_DLOG(ERROR) << __FUNCTION__ << ": Socket (" << socket << ") "
<< "is watched by Waiter (" << socket->waiter << "), "
<< "not this Waiter (" << this << ").";
SB_DSTACK(ERROR);
return false;
}
Waitee* waitee = RemoveWaitee(socket);
if (!waitee) {
return false;
}
event_del(&waitee->event);
socket->waiter = kSbSocketWaiterInvalid;
delete waitee;
return true;
}
void SbSocketWaiterPrivate::Wait() {
SB_DCHECK(SbThreadIsCurrent(thread_));
// We basically wait for the largest amount of time to achieve an indefinite
// block.
WaitTimed(kSbTimeMax);
}
SbSocketWaiterResult SbSocketWaiterPrivate::WaitTimed(SbTime duration) {
SB_DCHECK(SbThreadIsCurrent(thread_));
// The way to do this is apparently to create a timeout event, call WakeUp
// inside that callback, and then just do a normal wait.
struct event event;
timeout_set(&event, &SbSocketWaiterPrivate::LibeventTimeoutCallback, this);
event_base_set(base_, &event);
if (duration < kSbTimeMax) {
struct timeval tv;
ToTimevalDuration(duration, &tv);
timeout_add(&event, &tv);
}
waiting_ = true;
event_base_loop(base_, 0);
waiting_ = false;
SbSocketWaiterResult result =
woken_up_ ? kSbSocketWaiterResultWokenUp : kSbSocketWaiterResultTimedOut;
woken_up_ = false;
if (duration < kSbTimeMax) {
// We clean this up, in case we were awakened early, to prevent a spurious
// wake-up later.
timeout_del(&event);
}
return result;
}
void SbSocketWaiterPrivate::WakeUp(bool timeout) {
// We may be calling from a separate thread, so we have to be clever. The
// version of libevent we are using (14.x) does not really do thread-safety,
// despite the documentation that says otherwise. But, sending a byte through
// a local pipe gets the job done safely.
char buf = timeout ? 0 : 1;
int bytes_written = HANDLE_EINTR(write(wakeup_write_fd_, &buf, 1));
SB_DCHECK(bytes_written == 1 || errno == EAGAIN)
<< "[bytes_written:" << bytes_written << "] [errno:" << errno << "]";
}
// static
void SbSocketWaiterPrivate::LibeventSocketCallback(int fd,
int16_t event,
void* context) {
Waitee* waitee = reinterpret_cast<Waitee*>(context);
waitee->waiter->HandleSignal(waitee, event);
}
// static
void SbSocketWaiterPrivate::LibeventTimeoutCallback(int fd,
int16_t event,
void* context) {
reinterpret_cast<SbSocketWaiter>(context)->WakeUp(true);
}
// static
void SbSocketWaiterPrivate::LibeventWakeUpCallback(int fd,
int16_t event,
void* context) {
reinterpret_cast<SbSocketWaiter>(context)->HandleWakeUpRead();
}
void SbSocketWaiterPrivate::HandleSignal(Waitee* waitee,
short events) { // NOLINT[runtime/int]
int interests = 0;
if (events & EV_READ) {
interests |= kSbSocketWaiterInterestRead;
}
if (events & EV_WRITE) {
interests |= kSbSocketWaiterInterestWrite;
}
// Remove the non-persistent waitee before calling the callback, so that we
// can add another waitee in the callback if we need to. This is also why we
// copy all the fields we need out of waitee.
SbSocket socket = waitee->socket;
void* context = waitee->context;
SbSocketWaiterCallback callback = waitee->callback;
if (!waitee->persistent) {
Remove(waitee->socket);
}
callback(this, socket, context, interests);
}
void SbSocketWaiterPrivate::HandleWakeUpRead() {
SB_DCHECK(waiting_);
// Remove and discard the wakeup byte.
char buf;
int bytes_read = HANDLE_EINTR(read(wakeup_read_fd_, &buf, 1));
SB_DCHECK(bytes_read == 1);
if (buf != 0) {
woken_up_ = true;
}
event_base_loopbreak(base_);
}
void SbSocketWaiterPrivate::AddWaitee(Waitee* waitee) {
waitees_.insert(std::make_pair(waitee->socket, waitee));
}
SbSocketWaiterPrivate::Waitee* SbSocketWaiterPrivate::GetWaitee(
SbSocket socket) {
WaiteesMap::iterator it = waitees_.find(socket);
if (it == waitees_.end()) {
return NULL;
}
return it->second;
}
SbSocketWaiterPrivate::Waitee* SbSocketWaiterPrivate::RemoveWaitee(
SbSocket socket) {
WaiteesMap::iterator it = waitees_.find(socket);
if (it == waitees_.end()) {
return NULL;
}
Waitee* result = it->second;
waitees_.erase(it);
return result;
}