/* * 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 using std::string; using std::unique_ptr; namespace folly { AsyncPipeReader::~AsyncPipeReader() { close(); } void AsyncPipeReader::failRead(const AsyncSocketException& ex) { VLOG(5) << "AsyncPipeReader(this=" << this << ", fd=" << fd_ << "): failed while reading: " << ex.what(); DCHECK(readCallback_ != nullptr); AsyncReader::ReadCallback* callback = readCallback_; readCallback_ = nullptr; callback->readErr(ex); close(); } void AsyncPipeReader::close() { unregisterHandler(); if (fd_ != NetworkSocket()) { changeHandlerFD(NetworkSocket()); if (closeCb_) { closeCb_(fd_); } else { netops::close(fd_); } fd_ = NetworkSocket(); } } #ifdef _WIN32 static int recv_internal(NetworkSocket s, void* buf, size_t count) { auto r = netops::recv(s, buf, count, 0); if (r == -1 && WSAGetLastError() == WSAEWOULDBLOCK) { errno = EAGAIN; } return folly::to_narrow(r); } #endif void AsyncPipeReader::handlerReady(uint16_t events) noexcept { DestructorGuard dg(this); CHECK(events & EventHandler::READ); VLOG(5) << "AsyncPipeReader::handlerReady() this=" << this << ", fd=" << fd_; assert(readCallback_ != nullptr); while (readCallback_) { // - What API does callback support? const auto movable = readCallback_->isBufferMovable(); // noexcept // Get the buffer to read into. void* buf = nullptr; size_t buflen = 0; std::unique_ptr ioBuf; if (movable) { ioBuf = IOBuf::create(readCallback_->maxBufferSize()); buf = ioBuf->writableBuffer(); buflen = ioBuf->capacity(); } else { try { readCallback_->getReadBuffer(&buf, &buflen); } catch (const std::exception& ex) { AsyncSocketException aex( AsyncSocketException::BAD_ARGS, string("ReadCallback::getReadBuffer() " "threw exception: ") + ex.what()); failRead(aex); return; } catch (...) { AsyncSocketException aex( AsyncSocketException::BAD_ARGS, string("ReadCallback::getReadBuffer() " "threw non-exception type")); failRead(aex); return; } if (buf == nullptr || buflen == 0) { AsyncSocketException aex( AsyncSocketException::INVALID_STATE, string("ReadCallback::getReadBuffer() " "returned empty buffer")); failRead(aex); return; } } // Perform the read #ifdef _WIN32 // On Windows you can't call read on a socket, so call recv instead. ssize_t bytesRead = folly::fileutil_detail::wrapNoInt(recv_internal, fd_, buf, buflen); #else ssize_t bytesRead = folly::readNoInt(fd_.toFd(), buf, buflen); #endif if (bytesRead > 0) { if (movable) { ioBuf->append(std::size_t(bytesRead)); readCallback_->readBufferAvailable(std::move(ioBuf)); } else { readCallback_->readDataAvailable(size_t(bytesRead)); } // Fall through and continue around the loop if the read // completely filled the available buffer. // Note that readCallback_ may have been uninstalled or changed inside // readDataAvailable(). if (static_cast(bytesRead) < buflen) { return; } } else if (bytesRead < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // No more data to read right now. return; } else if (bytesRead < 0) { AsyncSocketException ex( AsyncSocketException::INVALID_STATE, "read failed", errno); failRead(ex); return; } else { assert(bytesRead == 0); // EOF unregisterHandler(); AsyncReader::ReadCallback* callback = readCallback_; readCallback_ = nullptr; callback->readEOF(); return; } // Max reads per loop? } } void AsyncPipeWriter::write( unique_ptr buf, AsyncWriter::WriteCallback* callback) { if (closed()) { if (callback) { AsyncSocketException ex( AsyncSocketException::NOT_OPEN, "attempt to write to closed pipe"); callback->writeErr(0, ex); } return; } bool wasEmpty = (queue_.empty()); folly::IOBufQueue iobq; iobq.append(std::move(buf)); std::pair p( std::move(iobq), callback); queue_.emplace_back(std::move(p)); if (wasEmpty) { handleWrite(); } else { CHECK(!queue_.empty()); CHECK(isHandlerRegistered()); } } void AsyncPipeWriter::writeChain( folly::AsyncWriter::WriteCallback* callback, std::unique_ptr&& buf, WriteFlags) { write(std::move(buf), callback); } void AsyncPipeWriter::closeOnEmpty() { VLOG(5) << "close on empty"; if (queue_.empty()) { closeNow(); } else { closeOnEmpty_ = true; CHECK(isHandlerRegistered()); } } void AsyncPipeWriter::closeNow() { VLOG(5) << "close now"; if (!queue_.empty()) { failAllWrites(AsyncSocketException( AsyncSocketException::NOT_OPEN, "closed with pending writes")); } if (fd_ != NetworkSocket()) { unregisterHandler(); changeHandlerFD(NetworkSocket()); if (closeCb_) { closeCb_(fd_); } else { netops::close(fd_); } fd_ = NetworkSocket(); } } void AsyncPipeWriter::failAllWrites(const AsyncSocketException& ex) { DestructorGuard dg(this); while (!queue_.empty()) { // the first entry of the queue could have had a partial write, but needs to // be tracked. if (queue_.front().second) { queue_.front().second->writeErr(0, ex); } queue_.pop_front(); } } void AsyncPipeWriter::handlerReady(uint16_t events) noexcept { CHECK(events & EventHandler::WRITE); handleWrite(); } #ifdef _WIN32 static int send_internal(NetworkSocket s, const void* buf, size_t count) { auto r = netops::send(s, buf, count, 0); if (r == -1 && WSAGetLastError() == WSAEWOULDBLOCK) { errno = EAGAIN; } return folly::to_narrow(r); } #endif void AsyncPipeWriter::handleWrite() { DestructorGuard dg(this); assert(!queue_.empty()); do { auto& front = queue_.front(); folly::IOBufQueue& curQueue = front.first; DCHECK(!curQueue.empty()); // someday, support writev. The logic for partial writes is a bit complex const IOBuf* head = curQueue.front(); CHECK(head->length()); #ifdef _WIN32 // On Windows you can't call write on a socket. ssize_t rc = folly::fileutil_detail::wrapNoInt( send_internal, fd_, head->data(), head->length()); #else ssize_t rc = folly::writeNoInt(fd_.toFd(), head->data(), head->length()); #endif if (rc < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // pipe is full VLOG(5) << "write blocked"; registerHandler(EventHandler::WRITE); return; } else { failAllWrites(AsyncSocketException( AsyncSocketException::INTERNAL_ERROR, "write failed", errno)); closeNow(); return; } } else if (rc == 0) { registerHandler(EventHandler::WRITE); return; } curQueue.trimStart(size_t(rc)); if (curQueue.empty()) { auto cb = front.second; queue_.pop_front(); if (cb) { cb->writeSuccess(); } } else { VLOG(5) << "partial write blocked"; } } while (!queue_.empty()); if (closeOnEmpty_) { closeNow(); } else { unregisterHandler(); } } } // namespace folly