Refuse loads and renders that would not fit in memory
Opening a project of ninety 17 MP photos got Effector killed by the kernel's OOM killer: 4.6 GB of decoded RGB on a machine with 3.6 GB free, and no core dump to show for it. Bulk allocations are now sized first and refused with an OutOfMemoryError past three quarters of the free memory (MemAvailable, or less under a cgroup memory.max). loadImages sizes the batch from the file headers and refuses it before decoding anything, and holds the decodes in flight to what is left; renderChain and renderFrames refuse a canvas at 24 bytes a pixel, plus any picture a Path param loads. The interface keeps a refused batch's paths, so saving the project never drops them. Version 0.6.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "cli/options.hpp"
|
||||
#include "core/canvas.hpp"
|
||||
#include "core/chain.hpp"
|
||||
#include "core/memory.hpp"
|
||||
#include "core/project.hpp"
|
||||
#include "core/registry.hpp"
|
||||
#include "core/rng.hpp"
|
||||
@@ -1718,6 +1719,72 @@ int main() {
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
|
||||
std::cout << "memory\n";
|
||||
{
|
||||
check(availableMemory() > 0, "the free memory can be read");
|
||||
check(formatBytes(std::size_t{5} << 29) == "2.5 GB" && formatBytes(std::size_t{380} << 20) == "380 MB",
|
||||
"sizes are written in GB and MB");
|
||||
|
||||
const auto dir = std::filesystem::temp_directory_path() / "effector_test_memory";
|
||||
std::filesystem::create_directories(dir);
|
||||
std::vector<std::string> paths;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const auto path = dir / ("src" + std::to_string(i) + ".png");
|
||||
gradient(400, 300, i + 1).save(path.string());
|
||||
paths.push_back(path.string());
|
||||
}
|
||||
check(imageFilePixels(paths[0]) == 400u * 300 && imageFilePixels((dir / "missing.png").string()) == 0,
|
||||
"an image is sized from its header");
|
||||
|
||||
// Three 400 x 300 images hold 1 MB decoded; with 1 MB free they do not fit.
|
||||
limitAvailableMemory(std::size_t{1} << 20);
|
||||
std::atomic<int> decoded{0};
|
||||
LoadProgress counting;
|
||||
counting.onFile = [&](std::size_t, std::size_t, const std::string&) { ++decoded; };
|
||||
std::vector<LoadFailure> failures;
|
||||
bool refused = false;
|
||||
std::string message;
|
||||
try {
|
||||
loadImages(paths, failures, &counting);
|
||||
} catch (const OutOfMemoryError& error) {
|
||||
refused = true;
|
||||
message = error.what();
|
||||
}
|
||||
check(refused, "a batch too large for the free memory is refused");
|
||||
check(decoded.load() == 0, "before a single file is decoded");
|
||||
check(message.find("Loading these 3 images needs about") == 0 && message.find("with 1 MB free") != std::string::npos,
|
||||
"and the message says how much is needed and how much is free");
|
||||
|
||||
const std::vector<ChainStage> scanlines = {{effectByName("scanlines"), {}}};
|
||||
const std::vector<Image> small = {gradient(64, 64, 7)};
|
||||
bool renderRefused = false;
|
||||
try {
|
||||
Rng rng(1);
|
||||
renderChain(scanlines, small, 4000, 4000, rng);
|
||||
} catch (const OutOfMemoryError&) {
|
||||
renderRefused = true;
|
||||
}
|
||||
check(renderRefused, "a render too large for the free memory is refused");
|
||||
bool videoRefused = false;
|
||||
try {
|
||||
VideoSettings clip;
|
||||
clip.enabled = true;
|
||||
renderFrames(scanlines, small, 4000, 4000, 1, {}, clip, [](int, Image&&) { return true; });
|
||||
} catch (const OutOfMemoryError&) {
|
||||
videoRefused = true;
|
||||
}
|
||||
check(videoRefused, "and so is a clip");
|
||||
Rng smallRng(1);
|
||||
check(renderChain(scanlines, small, 100, 100, smallRng).width() == 100,
|
||||
"a render that fits still runs");
|
||||
|
||||
limitAvailableMemory(0);
|
||||
failures.clear();
|
||||
check(loadImages(paths, failures).size() == 3, "with the memory there the batch loads");
|
||||
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
|
||||
std::cout << "video\n";
|
||||
{
|
||||
using effector::ChainStage;
|
||||
|
||||
Reference in New Issue
Block a user