Files
oak-editor/app/render/renderworkerpool.h
T
Mike-Solar ea78558a31 render: reference-count graph snapshots to prevent premature deletion
Proxy generation modifies the project, causing graph snapshots to be
rewritten. The old implementation deleted the previous snapshot
immediately, even though queued render jobs still held its path. The
worker then failed to load the deleted file.

Add per-path reference counting:
- Increment when a job is created with a snapshot path.
- Decrement when the job is processed, cancelled or removed.
- Only delete a snapshot when its reference count reaches zero and it
  is no longer the cached snapshot for the project.

Also keep snapshots in the system temp directory as before, since the
location itself was not the bug.
2026-07-13 13:43:20 +08:00

175 lines
4.9 KiB
C++

/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef RENDERWORKERPOOL_H
#define RENDERWORKERPOOL_H
#include <QHash>
#include <QMutex>
#include <QSet>
#include <QThread>
#include <QVector>
#include <QWaitCondition>
#include <deque>
#include <memory>
#include "codec/frame.h"
#include "node/project/serializer/serializer.h"
#include "render/ipc/frameslotpool.h"
#include "render/ipc/ipcmessage.h"
#include "render/ipc/sharedmemoryregion.h"
#include "render/rendermanager.h"
class QProcess;
namespace olive
{
class Project;
class RenderWorkerPool : public QThread {
Q_OBJECT
public:
explicit RenderWorkerPool(DecoderCache *decoder_cache,
const QString &gpu_backend,
QObject *parent = nullptr);
~RenderWorkerPool() override;
bool SubmitFrame(RenderTicketPtr ticket,
const RenderManager::RenderVideoParams &params);
bool RemoveTicket(RenderTicketPtr ticket);
void Shutdown();
protected:
void run() override;
private:
struct Job {
Job(RenderTicketPtr t, const RenderManager::RenderVideoParams &p)
: ticket(t)
, params(p)
{
}
RenderTicketPtr ticket;
RenderManager::RenderVideoParams params;
QString graph_path;
QString node_token;
QVector<FramePtr> input_frames;
};
enum class JobResult {
kFinished,
kRetryableFailure,
kFatalFailure,
kCancelled
};
struct ActiveJob {
RenderTicketPtr ticket;
qint64 process_id = 0;
qint64 ticket_id = 0;
};
struct PooledWorker {
QProcess *process = nullptr;
QString loaded_graph_path;
qint64 last_used_ms = 0;
int use_count = 0;
// Persistent shared memory for this worker. Reusing regions across frames
// avoids the cost of creating/destroying large shm segments every render.
ipc::SharedMemoryRegion output_region;
ipc::FrameSlotPool output_pool;
size_t output_slot_bytes = 0;
QString output_shm_key;
ipc::SharedMemoryRegion input_region;
ipc::FrameSlotPool input_pool;
size_t input_slot_bytes = 0;
QString input_shm_key;
};
struct CachedGraph {
QString path;
};
bool PrepareJob(RenderTicketPtr ticket,
const RenderManager::RenderVideoParams &params,
Job *job);
bool WriteGraphSnapshot(Project *project, QString *path);
bool IsSupported(const RenderManager::RenderVideoParams &params) const;
void WorkerLoop(int worker_index,
std::vector<std::unique_ptr<PooledWorker>> *local_pool);
void ProcessJob(const Job &job, int worker_index,
std::vector<std::unique_ptr<PooledWorker>> *local_pool);
JobResult ProcessJobAttempt(const Job &job, int worker_index,
int attempt_index,
PooledWorker *worker);
void FinishWithFrame(RenderTicketPtr ticket, const ipc::FrameSlotPool &pool,
uint32_t slot);
void CleanupGraphFile(const QString &path);
void AddGraphPathRef(const QString &path);
void AddGraphPathRefLocked(const QString &path);
void ReleaseGraphPathRef(const QString &path);
void ReleaseGraphPathRefLocked(const QString &path);
void SetGraphPathCached(const QString &path, bool cached);
void SetGraphPathCachedLocked(const QString &path, bool cached);
void CancelActiveProcess(qint64 process_id);
void SetActiveWorker(int worker_index, RenderTicketPtr ticket,
QProcess *worker, qint64 ticket_id);
void ClearActiveWorker(int worker_index, qint64 process_id);
int WorkerCount() const;
std::unique_ptr<PooledWorker> AcquireWorker(
std::vector<std::unique_ptr<PooledWorker>> *local_pool,
const QString &graph_path);
void ReturnWorker(std::vector<std::unique_ptr<PooledWorker>> *local_pool,
std::unique_ptr<PooledWorker> worker, bool keep_alive);
void ShutdownWorker(PooledWorker *worker);
void ShutdownLocalPool(std::vector<std::unique_ptr<PooledWorker>> *local_pool);
void ClearGraphCache();
DecoderCache *decoder_cache_;
QString gpu_backend_;
QMutex mutex_;
QWaitCondition wait_;
std::deque<Job> queue_;
bool stopping_ = false;
QVector<ActiveJob> active_jobs_;
QHash<QUuid, CachedGraph> graph_cache_;
QHash<QString, int> graph_path_ref_count_;
QSet<QString> cached_graph_paths_;
static constexpr uint32_t kOutputSlots = 2;
static constexpr int kMaxAttempts = 2;
static constexpr int kMaxWidth = 4096;
static constexpr int kMaxHeight = 2160;
static constexpr int kWorkerIdleTimeoutMs = 30000;
static constexpr int kWorkerMaxUses = 100;
};
}
#endif // RENDERWORKERPOOL_H