Memory Issues with Cairo-based Native Module
I’m working on a native C++ addon for Node.js that handles image processing with Cairo. The module works fine during local testing, but crashes with segmentation faults when running in production.
The Problem
My addon converts graphics data directly without using intermediate buffers. This works great on my development machine and even on production servers when tested standalone. However, when the same code runs as a background service managed by job queues, I get memory corruption errors.
The crashes happen somewhere in Cairo’s internals, making it hard to debug with gdb since the stack trace doesn’t point to my code.
Code Structure
Here’s the main function that processes image data:
Handle<Value>
Graphics::ProcessImage(const Arguments &args) {
HandleScope scope;
Graphics *graphics = ObjectWrap::Unwrap<Graphics>(args.This());
if (args.Length() < 1) {
return ThrowException(Exception::TypeError(String::New("ProcessImage requires one argument")));
}
Local<Object> obj = args[0]->ToObject();
ImageData *imgData = ObjectWrap::Unwrap<ImageData>(obj);
graphics->transferPixelData(imgData);
return Undefined();
}
void Graphics::transferPixelData(ImageData *imgData) {
if (this->isPDFType()) {
cairo_surface_finish(this->surface());
buffer_closure_t *closure = (buffer_closure_t *) this->closure();
int width = cairo_image_surface_get_width(this->surface());
int height = cairo_image_surface_get_height(this->surface());
imgData->setFromBuffer(closure->data, width, height);
}
else {
cairo_surface_flush(this->surface());
int width = cairo_image_surface_get_width(this->surface());
int height = cairo_image_surface_get_height(this->surface());
imgData->setFromBuffer(cairo_image_surface_get_data(this->surface()), width, height);
}
}
And the image data processing method:
cairo_status_t
ImageData::setFromBuffer(uint8_t *buffer, int w, int h) {
this->cleanup();
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w);
this->_surface = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_ARGB32, w, h, stride);
this->data_mode = BUFFER_MODE;
this->markReady();
cairo_status_t status = cairo_surface_status(_surface);
if (status) return status;
return CAIRO_STATUS_SUCCESS;
}
Questions
- What could cause memory issues only in production environments with job queues?
- How can I properly manage memory between Cairo and V8’s garbage collector?
- Any tips for debugging native modules that crash in external libraries?
I’ve added HandleScopes everywhere and the module passes all my local tests. Any advice would be really helpful.