blob: b8407a34ff834cf1302b1cd566d5b43515d3ce39 [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "ReceiveQueue.h"
#include <utility>
#include "MessageImpl.h"
#include "lib/ExecutorService.h"
namespace pulsar::st {
ReceiveQueue::ReceiveQueue(pulsar::ExecutorServicePtr executor, std::size_t capacity)
: executor_(std::move(executor)), capacity_(capacity) {}
std::deque<detail::Promise<void>> ReceiveQueue::takeCapacityWaitersIfRoomLocked() {
std::deque<detail::Promise<void>> toSignal;
if (buffer_.size() < capacity_ && !capacityWaiters_.empty()) {
toSignal = std::move(capacityWaiters_);
capacityWaiters_.clear();
}
return toSignal;
}
Future<MessageImplPtr> ReceiveQueue::receiveAsync() {
detail::Promise<MessageImplPtr> promise;
MessageImplPtr message;
std::deque<detail::Promise<void>> toSignal;
{
std::lock_guard<std::mutex> lock(mutex_);
if (closed_) {
promise.setError(Error{ResultAlreadyClosed, "consumer is closed"});
return promise.getFuture();
}
if (!buffer_.empty()) {
message = std::move(buffer_.front());
buffer_.pop_front();
toSignal = takeCapacityWaitersIfRoomLocked();
} else {
pendingReceives_.emplace(nextReceiveId_++, PendingReceive{promise, nullptr});
}
}
for (auto& waiter : toSignal) waiter.setSuccess();
if (message) promise.setValue(std::move(message));
return promise.getFuture();
}
Future<MessageImplPtr> ReceiveQueue::receiveAsync(std::chrono::milliseconds timeout) {
detail::Promise<MessageImplPtr> promise;
MessageImplPtr message;
std::deque<detail::Promise<void>> toSignal;
std::uint64_t receiveId = 0;
bool parked = false;
{
std::lock_guard<std::mutex> lock(mutex_);
if (closed_) {
promise.setError(Error{ResultAlreadyClosed, "consumer is closed"});
return promise.getFuture();
}
if (!buffer_.empty()) {
message = std::move(buffer_.front());
buffer_.pop_front();
toSignal = takeCapacityWaitersIfRoomLocked();
} else {
receiveId = nextReceiveId_++;
pendingReceives_.emplace(receiveId, PendingReceive{promise, nullptr});
parked = true;
}
}
for (auto& waiter : toSignal) waiter.setSuccess();
if (message) {
promise.setValue(std::move(message));
return promise.getFuture();
}
if (parked) {
// Attach the timer to the parked entry so delivery (or close) can cancel it — otherwise
// every timed receive would leave a live timer (holding this queue) until its deadline.
auto timer = executor_->createDeadlineTimer();
bool armed = false;
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = pendingReceives_.find(receiveId);
if (it != pendingReceives_.end()) {
it->second.timer = timer;
armed = true;
}
}
// If a message (or close) already completed the receive, the timer is never started.
if (armed) {
timer->expires_from_now(timeout);
auto self = shared_from_this(); // keep the queue alive until the timer fires
timer->async_wait([self, receiveId, promise, timer](const ASIO_ERROR& ec) {
if (ec) return; // cancelled: a message (or close) won the race
{
std::lock_guard<std::mutex> lock(self->mutex_);
auto it = self->pendingReceives_.find(receiveId);
if (it == self->pendingReceives_.end()) return; // a message was delivered first
self->pendingReceives_.erase(it);
}
promise.setError(Error{ResultTimeout, "receive timed out"});
});
}
}
return promise.getFuture();
}
Future<void> ReceiveQueue::offer(MessageImplPtr message) {
detail::Promise<MessageImplPtr> receiver;
DeadlineTimerPtr receiverTimer;
bool deliver = false;
detail::Promise<void> capacityPromise;
bool hasRoom = false;
{
std::lock_guard<std::mutex> lock(mutex_);
if (closed_) {
hasRoom = true;
} else {
if (!pendingReceives_.empty()) {
auto oldest = pendingReceives_.begin(); // FIFO: lowest id
receiver = std::move(oldest->second.promise);
receiverTimer = std::move(oldest->second.timer);
pendingReceives_.erase(oldest);
deliver = true;
} else {
buffer_.push_back(std::move(message));
}
if (buffer_.size() < capacity_) {
hasRoom = true;
} else {
capacityWaiters_.push_back(capacityPromise);
}
}
}
if (receiverTimer) {
ASIO_ERROR ignored;
receiverTimer->cancel(ignored);
}
if (deliver) receiver.setValue(std::move(message));
if (hasRoom) {
detail::Promise<void> ready;
ready.setSuccess();
return ready.getFuture();
}
return capacityPromise.getFuture();
}
void ReceiveQueue::close() {
std::map<std::uint64_t, PendingReceive> pending;
std::deque<detail::Promise<void>> waiters;
{
std::lock_guard<std::mutex> lock(mutex_);
if (closed_) return;
closed_ = true;
pending.swap(pendingReceives_);
waiters.swap(capacityWaiters_);
buffer_.clear();
}
for (auto& [id, entry] : pending) {
if (entry.timer) {
ASIO_ERROR ignored;
entry.timer->cancel(ignored);
}
entry.promise.setError(Error{ResultAlreadyClosed, "consumer is closed"});
}
for (auto& waiter : waiters) waiter.setSuccess(); // let segment loops re-arm and see closed
}
} // namespace pulsar::st