/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 #include #include #include #include #include #include #include #include #include // Due to the way kernel headers are included, this may or may not be defined. // Number pulled from 3.10 kernel headers. #ifndef SO_REUSEPORT #define SO_REUSEPORT 15 #endif #if FOLLY_HAVE_VLA #define FOLLY_HAVE_VLA_01 1 #else #define FOLLY_HAVE_VLA_01 0 #endif namespace fsp = folly::portability::sockets; namespace folly { AsyncUDPSocket::AsyncUDPSocket(EventBase* evb) : EventHandler(CHECK_NOTNULL(evb)), readCallback_(nullptr), eventBase_(evb), fd_() { evb->dcheckIsInEventBaseThread(); } AsyncUDPSocket::~AsyncUDPSocket() { if (fd_ != NetworkSocket()) { close(); } } void AsyncUDPSocket::bind(const folly::SocketAddress& address) { NetworkSocket socket = netops::socket( address.getFamily(), SOCK_DGRAM, address.getFamily() != AF_UNIX ? IPPROTO_UDP : 0); if (socket == NetworkSocket()) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "error creating async udp socket", errno); } auto g = folly::makeGuard([&] { netops::close(socket); }); // put the socket in non-blocking mode int ret = netops::set_socket_non_blocking(socket); if (ret != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "failed to put socket in non-blocking mode", errno); } if (reuseAddr_) { // put the socket in reuse mode int value = 1; if (netops::setsockopt( socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "failed to put socket in reuse mode", errno); } } if (reusePort_) { // put the socket in port reuse mode int value = 1; if (netops::setsockopt( socket, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)) != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "failed to put socket in reuse_port mode", errno); } } if (busyPollUs_ > 0) { #ifdef SO_BUSY_POLL // Set busy_poll time in microseconds on the socket. // It sets how long socket will be in busy_poll mode when no event occurs. int value = busyPollUs_; if (netops::setsockopt( socket, SOL_SOCKET, SO_BUSY_POLL, &value, sizeof(value)) != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "failed to set SO_BUSY_POLL on the socket", errno); } #else /* SO_BUSY_POLL is not supported*/ throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "SO_BUSY_POLL is not supported", errno); #endif } if (rcvBuf_ > 0) { // Set the size of the buffer for the received messages in rx_queues. int value = rcvBuf_; if (netops::setsockopt( socket, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "failed to set SO_RCVBUF on the socket", errno); } } if (sndBuf_ > 0) { // Set the size of the buffer for the sent messages in tx_queues. int value = sndBuf_; if (netops::setsockopt( socket, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "failed to set SO_SNDBUF on the socket", errno); } } // If we're using IPv6, make sure we don't accept V4-mapped connections if (address.getFamily() == AF_INET6) { int flag = 1; if (netops::setsockopt( socket, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag))) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set IPV6_V6ONLY", errno); } } // bind to the address sockaddr_storage addrStorage; address.getAddress(&addrStorage); auto& saddr = reinterpret_cast(addrStorage); if (netops::bind(socket, &saddr, address.getActualSize()) != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "failed to bind the async udp socket for:" + address.describe(), errno); } // success g.dismiss(); fd_ = socket; ownership_ = FDOwnership::OWNS; // attach to EventHandler EventHandler::changeHandlerFD(fd_); if (address.getFamily() == AF_UNIX || address.getPort() != 0) { localAddress_ = address; } else { localAddress_.setFromLocalAddress(fd_); } } void AsyncUDPSocket::dontFragment(bool df) { (void)df; // to avoid potential unused variable warning #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) && \ defined(IP_PMTUDISC_WANT) if (address().getFamily() == AF_INET) { int v4 = df ? IP_PMTUDISC_DO : IP_PMTUDISC_WANT; if (netops::setsockopt(fd_, IPPROTO_IP, IP_MTU_DISCOVER, &v4, sizeof(v4))) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set DF with IP_MTU_DISCOVER", errno); } } #endif #if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) && \ defined(IPV6_PMTUDISC_WANT) if (address().getFamily() == AF_INET6) { int v6 = df ? IPV6_PMTUDISC_DO : IPV6_PMTUDISC_WANT; if (netops::setsockopt( fd_, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &v6, sizeof(v6))) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set DF with IPV6_MTU_DISCOVER", errno); } } #endif } void AsyncUDPSocket::setDFAndTurnOffPMTU() { #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_PROBE) if (address().getFamily() == AF_INET) { int v4 = IP_PMTUDISC_PROBE; if (folly::netops::setsockopt( fd_, IPPROTO_IP, IP_MTU_DISCOVER, &v4, sizeof(v4))) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set PMTUDISC_PROBE with IP_MTU_DISCOVER", errno); } } #endif #if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_PROBE) if (address().getFamily() == AF_INET6) { int v6 = IPV6_PMTUDISC_PROBE; if (folly::netops::setsockopt( fd_, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &v6, sizeof(v6))) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set PMTUDISC_PROBE with IPV6_MTU_DISCOVER", errno); } } #endif } void AsyncUDPSocket::setErrMessageCallback( ErrMessageCallback* errMessageCallback) { errMessageCallback_ = errMessageCallback; int err = (errMessageCallback_ != nullptr); #if defined(IP_RECVERR) if (address().getFamily() == AF_INET && netops::setsockopt(fd_, IPPROTO_IP, IP_RECVERR, &err, sizeof(err))) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set IP_RECVERR", errno); } #endif #if defined(IPV6_RECVERR) if (address().getFamily() == AF_INET6 && netops::setsockopt(fd_, IPPROTO_IPV6, IPV6_RECVERR, &err, sizeof(err))) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set IPV6_RECVERR", errno); } #endif (void)err; } void AsyncUDPSocket::setFD(NetworkSocket fd, FDOwnership ownership) { CHECK_EQ(NetworkSocket(), fd_) << "Already bound to another FD"; fd_ = fd; ownership_ = ownership; EventHandler::changeHandlerFD(fd_); localAddress_.setFromLocalAddress(fd_); } ssize_t AsyncUDPSocket::writeGSO( const folly::SocketAddress& address, const std::unique_ptr& buf, int gso) { // UDP's typical MTU size is 1500, so high number of buffers // really do not make sense. Optimize for buffer chains with // buffers less than 16, which is the highest I can think of // for a real use case. iovec vec[16]; size_t iovec_len = buf->fillIov(vec, sizeof(vec) / sizeof(vec[0])).numIovecs; if (UNLIKELY(iovec_len == 0)) { buf->coalesce(); vec[0].iov_base = const_cast(buf->data()); vec[0].iov_len = buf->length(); iovec_len = 1; } return writev(address, vec, iovec_len, gso); } ssize_t AsyncUDPSocket::write( const folly::SocketAddress& address, const std::unique_ptr& buf) { return writeGSO(address, buf, 0); } ssize_t AsyncUDPSocket::writev( const folly::SocketAddress& address, const struct iovec* vec, size_t iovec_len, int gso) { CHECK_NE(NetworkSocket(), fd_) << "Socket not yet bound"; sockaddr_storage addrStorage; address.getAddress(&addrStorage); struct msghdr msg; if (!connected_) { msg.msg_name = reinterpret_cast(&addrStorage); msg.msg_namelen = address.getActualSize(); } else { if (connectedAddress_ != address) { errno = ENOTSUP; return -1; } msg.msg_name = nullptr; msg.msg_namelen = 0; } msg.msg_iov = const_cast(vec); msg.msg_iovlen = iovec_len; msg.msg_control = nullptr; msg.msg_controllen = 0; msg.msg_flags = 0; #ifdef FOLLY_HAVE_MSG_ERRQUEUE if (gso > 0) { char control[CMSG_SPACE(sizeof(uint16_t))]; msg.msg_control = control; msg.msg_controllen = sizeof(control); struct cmsghdr* cm = CMSG_FIRSTHDR(&msg); cm->cmsg_level = SOL_UDP; cm->cmsg_type = UDP_SEGMENT; cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); auto gso_len = static_cast(gso); memcpy(CMSG_DATA(cm), &gso_len, sizeof(gso_len)); return sendmsg(fd_, &msg, 0); } #else CHECK_LT(gso, 1) << "GSO not supported"; #endif return sendmsg(fd_, &msg, 0); } ssize_t AsyncUDPSocket::writev( const folly::SocketAddress& address, const struct iovec* vec, size_t iovec_len) { return writev(address, vec, iovec_len, 0); } /** * Send the data in buffers to destination. Returns the return code from * ::sendmmsg. */ int AsyncUDPSocket::writem( const folly::SocketAddress& address, const std::unique_ptr* bufs, size_t count) { return writemGSO(address, bufs, count, nullptr); } int AsyncUDPSocket::writemGSO( const folly::SocketAddress& address, const std::unique_ptr* bufs, size_t count, const int* gso) { int ret; constexpr size_t kSmallSizeMax = 8; char* gsoControl = nullptr; #ifndef FOLLY_HAVE_MSG_ERRQUEUE CHECK(!gso) << "GSO not supported"; #endif if (count <= kSmallSizeMax) { // suppress "warning: variable length array 'vec' is used [-Wvla]" FOLLY_PUSH_WARNING FOLLY_GNU_DISABLE_WARNING("-Wvla") mmsghdr vec[BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallSizeMax)]; #ifdef FOLLY_HAVE_MSG_ERRQUEUE // we will allocate this on the stack anyway even if we do not use it char control [(BOOST_PP_IF(FOLLY_HAVE_VLA_01, count, kSmallSizeMax)) * (CMSG_SPACE(sizeof(uint16_t)))]; if (gso) { gsoControl = control; } #endif FOLLY_POP_WARNING ret = writeImpl(address, bufs, count, vec, gso, gsoControl); } else { std::unique_ptr vec(new mmsghdr[count]); #ifdef FOLLY_HAVE_MSG_ERRQUEUE std::unique_ptr control( gso ? (new char[count * (CMSG_SPACE(sizeof(uint16_t)))]) : nullptr); if (gso) { gsoControl = control.get(); } #endif ret = writeImpl(address, bufs, count, vec.get(), gso, gsoControl); } return ret; } void AsyncUDPSocket::fillMsgVec( sockaddr_storage* addr, socklen_t addr_len, const std::unique_ptr* bufs, size_t count, struct mmsghdr* msgvec, struct iovec* iov, size_t iov_count, const int* gso, char* gsoControl) { size_t remaining = iov_count; size_t iov_pos = 0; for (size_t i = 0; i < count; i++) { // we can use remaining here to avoid calling countChainElements() again auto ret = bufs[i]->fillIov(&iov[iov_pos], remaining); size_t iovec_len = ret.numIovecs; remaining -= iovec_len; auto& msg = msgvec[i].msg_hdr; msg.msg_name = reinterpret_cast(addr); msg.msg_namelen = addr_len; msg.msg_iov = &iov[iov_pos]; msg.msg_iovlen = iovec_len; #ifdef FOLLY_HAVE_MSG_ERRQUEUE if (gso && gso[i] > 0) { msg.msg_control = &gsoControl[i * CMSG_SPACE(sizeof(uint16_t))]; msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); struct cmsghdr* cm = CMSG_FIRSTHDR(&msg); cm->cmsg_level = SOL_UDP; cm->cmsg_type = UDP_SEGMENT; cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); auto gso_len = static_cast(gso[i]); memcpy(CMSG_DATA(cm), &gso_len, sizeof(gso_len)); } else { msg.msg_control = nullptr; msg.msg_controllen = 0; } #else (void)gso; (void)gsoControl; msg.msg_control = nullptr; msg.msg_controllen = 0; #endif msg.msg_flags = 0; msgvec[i].msg_len = 0; iov_pos += iovec_len; } } int AsyncUDPSocket::writeImpl( const folly::SocketAddress& address, const std::unique_ptr* bufs, size_t count, struct mmsghdr* msgvec, const int* gso, char* gsoControl) { sockaddr_storage addrStorage; address.getAddress(&addrStorage); size_t iov_count = 0; for (size_t i = 0; i < count; i++) { iov_count += bufs[i]->countChainElements(); } int ret; constexpr size_t kSmallSizeMax = 16; if (iov_count <= kSmallSizeMax) { // suppress "warning: variable length array 'vec' is used [-Wvla]" FOLLY_PUSH_WARNING FOLLY_GNU_DISABLE_WARNING("-Wvla") iovec iov[BOOST_PP_IF(FOLLY_HAVE_VLA_01, iov_count, kSmallSizeMax)]; FOLLY_POP_WARNING fillMsgVec( &addrStorage, folly::to_narrow(address.getActualSize()), bufs, count, msgvec, iov, iov_count, gso, gsoControl); ret = sendmmsg(fd_, msgvec, count, 0); } else { std::unique_ptr iov(new iovec[iov_count]); fillMsgVec( &addrStorage, folly::to_narrow(address.getActualSize()), bufs, count, msgvec, iov.get(), iov_count, gso, gsoControl); ret = sendmmsg(fd_, msgvec, count, 0); } return ret; } ssize_t AsyncUDPSocket::recvmsg(struct msghdr* msg, int flags) { return netops::recvmsg(fd_, msg, flags); } int AsyncUDPSocket::recvmmsg( struct mmsghdr* msgvec, unsigned int vlen, unsigned int flags, struct timespec* timeout) { return netops::recvmmsg(fd_, msgvec, vlen, flags, timeout); } void AsyncUDPSocket::resumeRead(ReadCallback* cob) { CHECK(!readCallback_) << "Another read callback already installed"; CHECK_NE(NetworkSocket(), fd_) << "UDP server socket not yet bind to an address"; readCallback_ = CHECK_NOTNULL(cob); if (!updateRegistration()) { AsyncSocketException ex( AsyncSocketException::NOT_OPEN, "failed to register for accept events"); readCallback_ = nullptr; cob->onReadError(ex); return; } } void AsyncUDPSocket::pauseRead() { // It is ok to pause an already paused socket readCallback_ = nullptr; updateRegistration(); } void AsyncUDPSocket::close() { eventBase_->dcheckIsInEventBaseThread(); if (readCallback_) { auto cob = readCallback_; readCallback_ = nullptr; cob->onReadClosed(); } // Unregister any events we are registered for unregisterHandler(); if (fd_ != NetworkSocket() && ownership_ == FDOwnership::OWNS) { netops::close(fd_); } fd_ = NetworkSocket(); } void AsyncUDPSocket::handlerReady(uint16_t events) noexcept { if (events & EventHandler::READ) { DCHECK(readCallback_); handleRead(); } } size_t AsyncUDPSocket::handleErrMessages() noexcept { #ifdef FOLLY_HAVE_MSG_ERRQUEUE if (errMessageCallback_ == nullptr) { return 0; } uint8_t ctrl[1024]; unsigned char data; struct msghdr msg; iovec entry; entry.iov_base = &data; entry.iov_len = sizeof(data); msg.msg_iov = &entry; msg.msg_iovlen = 1; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_control = ctrl; msg.msg_controllen = sizeof(ctrl); msg.msg_flags = 0; int ret; size_t num = 0; while (fd_ != NetworkSocket()) { ret = netops::recvmsg(fd_, &msg, MSG_ERRQUEUE); VLOG(5) << "AsyncSocket::handleErrMessages(): recvmsg returned " << ret; if (ret < 0) { if (errno != EAGAIN) { auto errnoCopy = errno; LOG(ERROR) << "::recvmsg exited with code " << ret << ", errno: " << errnoCopy; AsyncSocketException ex( AsyncSocketException::INTERNAL_ERROR, "recvmsg() failed", errnoCopy); failErrMessageRead(ex); } return num; } for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr && cmsg->cmsg_len != 0; cmsg = CMSG_NXTHDR(&msg, cmsg)) { ++num; errMessageCallback_->errMessage(*cmsg); if (fd_ == NetworkSocket()) { // once the socket is closed there is no use for more read errors. return num; } } } return num; #else return 0; #endif } void AsyncUDPSocket::failErrMessageRead(const AsyncSocketException& ex) { if (errMessageCallback_ != nullptr) { ErrMessageCallback* callback = errMessageCallback_; errMessageCallback_ = nullptr; callback->errMessageError(ex); } } int AsyncUDPSocket::connect(const folly::SocketAddress& address) { CHECK_NE(NetworkSocket(), fd_) << "Socket not yet bound"; sockaddr_storage addrStorage; address.getAddress(&addrStorage); int ret = netops::connect( fd_, reinterpret_cast(&addrStorage), address.getActualSize()); if (ret == 0) { connected_ = true; connectedAddress_ = address; } return ret; } void AsyncUDPSocket::handleRead() noexcept { void* buf{nullptr}; size_t len{0}; if (handleErrMessages()) { return; } if (fd_ == NetworkSocket()) { // The socket may have been closed by the error callbacks. return; } if (readCallback_->shouldOnlyNotify()) { return readCallback_->onNotifyDataAvailable(*this); } readCallback_->getReadBuffer(&buf, &len); if (buf == nullptr || len == 0) { AsyncSocketException ex( AsyncSocketException::BAD_ARGS, "AsyncUDPSocket::getReadBuffer() returned empty buffer"); auto cob = readCallback_; readCallback_ = nullptr; cob->onReadError(ex); updateRegistration(); return; } struct sockaddr_storage addrStorage; socklen_t addrLen = sizeof(addrStorage); memset(&addrStorage, 0, size_t(addrLen)); auto rawAddr = reinterpret_cast(&addrStorage); rawAddr->sa_family = localAddress_.getFamily(); ssize_t bytesRead; ReadCallback::OnDataAvailableParams params; #ifdef FOLLY_HAVE_MSG_ERRQUEUE if (gro_.has_value() && (gro_ != 0)) { char control[CMSG_SPACE(sizeof(uint16_t))] = {}; struct msghdr msg = {}; struct iovec iov = {}; struct cmsghdr* cmsg; uint16_t* grosizeptr; iov.iov_base = buf; iov.iov_len = len; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = rawAddr; msg.msg_namelen = addrLen; msg.msg_control = control; msg.msg_controllen = sizeof(control); bytesRead = netops::recvmsg(fd_, &msg, MSG_TRUNC); if (bytesRead >= 0) { addrLen = msg.msg_namelen; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) { grosizeptr = (uint16_t*)CMSG_DATA(cmsg); params.gro_ = *grosizeptr; break; } } } } else { bytesRead = netops::recvfrom(fd_, buf, len, MSG_TRUNC, rawAddr, &addrLen); } #else bytesRead = netops::recvfrom(fd_, buf, len, MSG_TRUNC, rawAddr, &addrLen); #endif if (bytesRead >= 0) { clientAddress_.setFromSockaddr(rawAddr, addrLen); if (bytesRead > 0) { bool truncated = false; if ((size_t)bytesRead > len) { truncated = true; bytesRead = ssize_t(len); } readCallback_->onDataAvailable( clientAddress_, size_t(bytesRead), truncated, params); } } else { if (errno == EAGAIN || errno == EWOULDBLOCK) { // No data could be read without blocking the socket return; } AsyncSocketException ex( AsyncSocketException::INTERNAL_ERROR, "::recvfrom() failed", errno); // In case of UDP we can continue reading from the socket // even if the current request fails. We notify the user // so that he can do some logging/stats collection if he wants. auto cob = readCallback_; readCallback_ = nullptr; cob->onReadError(ex); updateRegistration(); } } bool AsyncUDPSocket::updateRegistration() noexcept { uint16_t flags = NONE; if (readCallback_) { flags |= READ; } return registerHandler(uint16_t(flags | PERSIST)); } bool AsyncUDPSocket::setGSO(int val) { #ifdef FOLLY_HAVE_MSG_ERRQUEUE int ret = netops::setsockopt(fd_, SOL_UDP, UDP_SEGMENT, &val, sizeof(val)); gso_ = ret ? -1 : val; return !ret; #else (void)val; return false; #endif } int AsyncUDPSocket::getGSO() { // check if we can return the cached value if (FOLLY_UNLIKELY(!gso_.has_value())) { #ifdef FOLLY_HAVE_MSG_ERRQUEUE int gso = -1; socklen_t optlen = sizeof(gso); if (!netops::getsockopt(fd_, SOL_UDP, UDP_SEGMENT, &gso, &optlen)) { gso_ = gso; } else { gso_ = -1; } #else gso_ = -1; #endif } return gso_.value(); } bool AsyncUDPSocket::setGRO(bool bVal) { #ifdef FOLLY_HAVE_MSG_ERRQUEUE int val = bVal ? 1 : 0; int ret = netops::setsockopt(fd_, SOL_UDP, UDP_GRO, &val, sizeof(val)); gro_ = ret ? -1 : val; return !ret; #else (void)bVal; return false; #endif } int AsyncUDPSocket::getGRO() { // check if we can return the cached value if (FOLLY_UNLIKELY(!gro_.has_value())) { #ifdef FOLLY_HAVE_MSG_ERRQUEUE int gro = -1; socklen_t optlen = sizeof(gro); if (!netops::getsockopt(fd_, SOL_UDP, UDP_GRO, &gro, &optlen)) { gro_ = gro; } else { gro_ = -1; } #else gro_ = -1; #endif } return gro_.value(); } void AsyncUDPSocket::setTrafficClass(int tclass) { if (netops::setsockopt( fd_, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof(int)) != 0) { throw AsyncSocketException( AsyncSocketException::NOT_OPEN, "Failed to set IPV6_TCLASS", errno); } } void AsyncUDPSocket::applyOptions( const SocketOptionMap& options, SocketOptionKey::ApplyPos pos) { auto result = applySocketOptions(fd_, options, pos); if (result != 0) { throw AsyncSocketException( AsyncSocketException::INTERNAL_ERROR, "failed to set socket option", result); } } void AsyncUDPSocket::detachEventBase() { DCHECK(eventBase_ && eventBase_->isInEventBaseThread()); registerHandler(uint16_t(NONE)); eventBase_ = nullptr; EventHandler::detachEventBase(); } void AsyncUDPSocket::attachEventBase(folly::EventBase* evb) { DCHECK(!eventBase_); DCHECK(evb && evb->isInEventBaseThread()); eventBase_ = evb; EventHandler::attachEventBase(evb); updateRegistration(); } } // namespace folly