Ctrl K
ComunidadGuías…

queue_lazy_item.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
#pragma once
#include "queue_item_store.h"
#include "upnp/stream_server.h"
#include <condition_variable>
#include <algorithm>
#include <functional>
#include <mutex>

namespace queue_audio {
// A lazy route has an optional immutable WAV declaration and a separate
// producer. Deferred routes publish the natural declaration only after binding. Selection is queued exactly once; neither HTTP nor HEAD executes
// an LMS command. The room callback must only enqueue work and return promptly.
class LazyItem {
    std::shared_ptr<Item> declaration, produced;
    std::function<void()> activate;
    std::mutex mutex;
    std::condition_variable changed;
    bool requested = false, cancelled = false, activationClaimed = false;
    unsigned issuedCommands = 0;
    std::vector<upnp::StreamRequest *> readers;
public:
    LazyItem(std::shared_ptr<Item> declared, std::function<void()> enqueue, bool deferred = false)
        : declaration(std::move(declared)), activate(std::move(enqueue)) {
        if ((!deferred && (!declaration || !declaration->contentLength())) || !activate)
            throw std::invalid_argument("lazy item needs an immutable declaration and selector");
    }
    // The room worker must claim immediately before an LMS mutation. Enqueueing
    // from HTTP alone is not authority: the request may have left meanwhile.
    bool claimActivation() {
        std::lock_guard<std::mutex> lock(mutex);
        if (cancelled || produced || !requested || readers.empty() || activationClaimed) return false;
        const bool connected = std::any_of(readers.begin(), readers.end(), [](auto *request) {
            return !request->aborted() && !request->peerClosed();
        });
        if (!connected) {
            cancelled = true;
            changed.notify_all();
            return false;
        }
        activationClaimed = true;
        return true;
    }
    bool publish(std::shared_ptr<Item> item) {
        if (!item || (declaration && (item->room() != declaration->room() ||
            item->descriptor().rate != declaration->descriptor().rate ||
            item->descriptor().kind != declaration->descriptor().kind ||
            item->contentLength() != declaration->contentLength())))
            throw std::invalid_argument("lazy producer differs from declared WAV");
        std::lock_guard<std::mutex> lock(mutex);
        if (cancelled) return false; // late producer cannot resurrect an abandoned window
        if (produced && produced != item) throw std::logic_error("lazy producer already bound");
        produced = std::move(item);
        changed.notify_all();
        return true;
    }
    std::shared_ptr<Item> ready() {
        std::lock_guard<std::mutex> lock(mutex);
        return cancelled ? nullptr : produced;
    }
    bool abandoned() {
        std::lock_guard<std::mutex> lock(mutex);
        return cancelled;
    }
    bool waiting() {
        std::lock_guard<std::mutex> lock(mutex);
        return !cancelled && !produced && !readers.empty();
    }
    unsigned commandCount() {
        std::lock_guard<std::mutex> lock(mutex);
        return issuedCommands;
    }
    void commandIssued() {
        std::lock_guard<std::mutex> lock(mutex);
        ++issuedCommands;
    }
    void cancel() {
        std::lock_guard<std::mutex> lock(mutex);
        cancelled = true;
        changed.notify_all();
    }
    std::shared_ptr<Item> resolve(upnp::StreamRequest &request,
                                 std::chrono::milliseconds budget = std::chrono::seconds(15)) {
        const auto deadline = std::chrono::steady_clock::now() + budget;
        std::unique_lock<std::mutex> lock(mutex);
        if (cancelled || request.aborted() || request.peerClosed()) return {};
        readers.push_back(&request);
        // Every exit, including an exception from enqueue, releases its claim.
        // A surviving concurrent range reader keeps the same activation alive.
        struct ReaderLease {
            LazyItem &owner;
            std::unique_lock<std::mutex> &lock;
            upnp::StreamRequest *request;
            ~ReaderLease() {
                if (!lock.owns_lock()) lock.lock();
                owner.readers.erase(std::find(owner.readers.begin(), owner.readers.end(), request));
                if (owner.readers.empty() && !owner.produced) {
                    owner.cancelled = true;
                    owner.changed.notify_all();
                }
            }
        } lease{*this, lock, &request};
        if (!produced && !requested) {
            requested = true;
            lock.unlock();
            try { activate(); }
            catch (...) { cancel(); throw; }
            lock.lock();
        }
        while (!produced && !cancelled && !request.aborted() && !request.peerClosed()) {
            if (std::chrono::steady_clock::now() >= deadline) return {};
            changed.wait_until(lock, std::min(deadline,
                std::chrono::steady_clock::now() + std::chrono::milliseconds(50)));
        }
        return cancelled || request.aborted() || request.peerClosed() ? nullptr : produced;
    }
};
} // namespace queue_audio