Skip to content

Commit 228fe97

Browse files
author
Jeremy Daer
committed
Source.new_from_memory: keep a ref to the memory area
vips_source_new_from_memory() aliases the caller's buffer rather than copying it -- vips_blob_new(NULL, data, length), NULL free_fn -- so libvips reads it for as long as the source is alive. We kept no Ruby reference to it, so nothing stopped the GC from freeing it first. Mirrors Image.new_from_memory, which already does this. Also answers and removes the FIXME: Image.new_from_buffer does not need a ref. It goes through GValue#set for Vips::BLOB_TYPE, which g_mallocs its own block, memcpys into it and hands libvips ownership via vips_value_set_blob(..., GLib::G_FREE, ...).
1 parent 88bdc7c commit 228fe97

3 files changed

Lines changed: 28 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## master
44

5+
* `Source.new_from_memory` keeps a reference to the memory area, which libvips
6+
aliases rather than copies
7+
58
## Version 2.3.0 (2025-12-10)
69

710
* move library_name out of the global namespace and into FFI [jcupitt]

lib/vips/source.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ def self.new_from_file(filename)
7070
# Create a new source from an area of memory. Memory areas can be
7171
# strings, arrays and so forth -- anything that supports bytesize.
7272
#
73+
# libvips does not copy the memory area, so the source keeps a reference
74+
# to it and will hold it alive for as long as the source is alive.
75+
#
7376
# Pass sources to {Image.new_from_source} to load images from
7477
# them.
7578
#
@@ -79,10 +82,14 @@ def self.new_from_memory(data)
7982
ptr = Vips.vips_source_new_from_memory data, data.bytesize
8083
raise Vips::Error if ptr.null?
8184

82-
# FIXME do we need to keep a ref to the underlying memory area? what
83-
# about Image.new_from_buffer? Does that need a secret ref too?
85+
source = Vips::Source.new ptr
8486

85-
Vips::Source.new ptr
87+
# vips_source_new_from_memory() aliases the memory area rather than
88+
# copying it, so we must keep a secret ref to stop it being freed while
89+
# the source is alive. See {Image.new_from_memory}.
90+
source.references << data
91+
92+
source
8693
end
8794
end
8895
end

spec/connection_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@
2626
expect(source)
2727
end
2828

29+
it "can load an image from a memory source after the caller drops its ref" do
30+
source = Vips::Source.new_from_memory File.binread(simg("wagon.jpg"))
31+
32+
# the secret ref inside source is now the only thing keeping the string
33+
# alive ... GC to try to trigger a segv if new_from_memory didn't take one
34+
GC.start
35+
36+
image = Vips::Image.new_from_source source, ""
37+
38+
expect(image.width).to eq(685)
39+
expect(image.height).to eq(478)
40+
expect(image.bands).to eq(3)
41+
expect(image.avg).to be_within(0.001).of(109.789)
42+
end
43+
2944
it "sources have filenames and nicks" do
3045
source = Vips::Source.new_from_file simg("wagon.jpg")
3146

0 commit comments

Comments
 (0)