matplotplusplus icon indicating copy to clipboard operation
matplotplusplus copied to clipboard

Opening the gnuplot failed: pipe: Too many open files

Open steppenhahni opened this issue 9 months ago • 3 comments

Bug category

  • [ ] bug - compilation error
  • [ ] bug - compilation warning
  • [x ] bug - runtime error
  • [ ] bug - runtime warning
  • [ ] bug - logic error

Describe the bug Hi, I really like the library you created. I found a problem when creating a lot of plots. If a lot of plots are created silently and then saved, matplot seems not to close the file connections.

Steps to Reproduce

My minimal working example is:

#include #include <matplot/matplot.h> int main() {

using namespace matplot;

for (int i = 0; i < 1000; i++) {
	std::vector<double> a = {1.0,2.0,3.0,4.0};
	std::vector<double> b = {1.0,2.0,3.0,4.0};

	auto fig = figure(true);
	fig->size(1200, 800);
	auto ax = axes(fig);
	ax -> plot(a,b);
	save("test" + std::to_string(i) + ".svg");

}

}

Output

After around 251 plots (depends on system, I think), I get this console output for all following plots:

Opening the gnuplot failed: pipe: Too many open files Please install gnuplot 5.2.6+: http://www.gnuplot.info

Platform

  • [ ] cross-platform issue - linux
  • [ x] cross-platform issue - windows
  • [x ] cross-platform issue - macos

Environment Details:

  • OS: MacOS
  • OS Version: Sonoma 14.4
  • Compiler: g++-14 (cstd-20)
  • Compiler version:

Additional context

steppenhahni avatar Apr 07 '25 01:04 steppenhahni

Hello, would u share your solution please?

OctaAIVision avatar Jul 22 '25 16:07 OctaAIVision

I used standard GNU Plot library at the end to control the piping myself. It was not that hard overall...

steppenhahni avatar Jul 22 '25 18:07 steppenhahni

hey my friend @steppenhahni you can use this code sample. U only make 1 frame and keep updating it. I leave the class here. I'm pretty sure u can customize it for ur usage:

The Header File:

#ifndef SCALEHISTOGRAM_H
#define SCALEHISTOGRAM_H

#include <vector>
#include <string>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <atomic>
#include <queue>
#include <deque>
#include <boost/lockfree/spsc_queue.hpp>


#include "matplot/matplot.h"

/* NEW: small POD to move both pieces through the lock-free queue */
struct ScaleSample {
    double    value;
    long long ts;     // caller-provided timestamp
};

class ScaleHistogram {
public:
    // explicit ScaleHistogram(std::size_t numBins = 30, std::size_t maxFiles = 100);
    explicit ScaleHistogram(
        std::size_t bins = 50,
        std::size_t maxFiles = 100,
        std::size_t batch = 64,
        std::chrono::milliseconds flush = std::chrono::milliseconds(200));
    ~ScaleHistogram();

    /** Append a new scale and emit / overwrite a PNG. Thread-safe. */
    // void plot(double scale);

    /** wait-free push: supply scale + your own timestamp */
    void plotAsync(double scale, long long myTimeStamp);

    /** Stop async processing and cleanup */
    // void stopAsync();

private:
    void pruneOldPNGs();           // keep only maxFiles_ files
    std::string timeStamp() const;
    void asyncPlotWorker();  // Background thread worker


    // ─── config ───────────────────────────────────────
    const std::size_t bins_;
    const std::size_t maxFiles_;
    const std::size_t batchSize_;
    const std::chrono::milliseconds flushInterval_;

    // ─── data received from producers (SPSC, lock-free) ───
    boost::lockfree::spsc_queue<ScaleSample, boost::lockfree::capacity<8192>> q_;

    // ─── worker thread ────────────────────────────────
    std::thread  worker_;
    std::atomic<bool> stop_{false};

    // ─── persistent Matplot++ artefacts ───────────────
    std::vector<double> all_;     // full history
    matplot::figure_handle fig_;  // created once
    matplot::histogram_handle   hist_;// handle to bars
    matplot::axes_handle   ax_;      //  ← NEW
    // bool firstDraw_{true};           //  ← NEW

    // ─── housekeeping ────────────────────────────────
    std::deque<std::string> written_;
};


#endif //SCALEHISTOGRAM_H

.cc FIle:


#include "ScaleHistogram.h"
#include <filesystem>
#include <chrono>
#include <iomanip>
#include <sstream>

namespace plt = matplot;
namespace fs  = std::filesystem;
ScaleHistogram::ScaleHistogram(std::size_t bins,
                               std::size_t maxFiles,
                               std::size_t batch,
                               std::chrono::milliseconds flush)
    : bins_(bins),
      maxFiles_(maxFiles),
      batchSize_(batch),
      flushInterval_(flush),
      q_()
{
    fs::create_directories("plots");

    fig_  = plt::figure(true);                // head-less
    ax_  = fig_->current_axes();

    ax_->title("Histogram of Scales");
    ax_->xlabel("scale");
    ax_->ylabel("count");
    ax_->hist(std::vector<double>{}, bins_);   // empty bars

    // hist_ = plt::hist(std::vector<double>{}, bins_); // empty bars
    // plt::title("Histogram of Scales");
    // plt::xlabel("scale");
    // plt::ylabel("count");

    worker_ = std::thread(&ScaleHistogram::asyncPlotWorker, this);
}

ScaleHistogram::~ScaleHistogram()
{
    stop_.store(true, std::memory_order_relaxed);
    if (worker_.joinable()) worker_.join();
}

/* ---------- PUBLIC: wait-free push ---------- */
void ScaleHistogram::plotAsync(double scale, long long myTimeStamp)
{
    /* drops sample if queue momentarily full */
    q_.push(ScaleSample{scale, myTimeStamp});
}

/* ---------- WORKER ---------- */
void ScaleHistogram::asyncPlotWorker()
{
    std::vector<double> local;
    local.reserve(batchSize_);
    long long lastTS = 0;                                 // remember latest ts

    auto nextFlush = std::chrono::steady_clock::now() + flushInterval_;

    while (!stop_.load(std::memory_order_relaxed)) {

        ScaleSample s;
        while (q_.pop(s)) {
            local.push_back(s.value);
            lastTS = s.ts;                                // keep the most recent
            if (local.size() >= batchSize_)
                break;
        }

        const auto now = std::chrono::steady_clock::now();
        if (local.empty() && now < nextFlush) {
            std::this_thread::sleep_until(nextFlush);
            continue;
        }

        /* move batch into history */
        all_.insert(all_.end(),
                    std::make_move_iterator(local.begin()),
                    std::make_move_iterator(local.end()));
        local.clear();
        if (all_.empty()) {                     // ← NEW: nothing to draw yet
            nextFlush = std::chrono::steady_clock::now() + flushInterval_;
            continue;
        }
        // redraw just the bars
        ax_->clear();                      // wipes only series; title/labels stay
        ax_->hist(all_, bins_);

        const std::string file =
            "plots/ScaleHistogram_" + std::to_string(lastTS) + ".png";
        plt::save(fig_, file);
        written_.push_back(file);

        pruneOldPNGs();
        nextFlush = std::chrono::steady_clock::now() + flushInterval_;
    }
}

/* ---------- helpers unchanged except for includes ---------- */
void ScaleHistogram::pruneOldPNGs()
{
    while (written_.size() > maxFiles_) {
        std::error_code es;
        fs::remove(written_.front(), es);
        written_.pop_front();
    }
}

std::string ScaleHistogram::timeStamp() const
{
    using clock = std::chrono::system_clock;
    auto now = clock::now();
    auto tt  = clock::to_time_t(now);
    auto ms  = std::chrono::duration_cast<std::chrono::milliseconds>(
                 now.time_since_epoch()) % 1000;

    std::ostringstream ss;
    ss << std::put_time(std::localtime(&tt), "%Y%m%d_%H%M%S")
       << '_' << std::setw(3) << std::setfill('0') << ms.count();
    return ss.str();
}

Sadegh-Kalami avatar Jul 23 '25 14:07 Sadegh-Kalami