diff --git a/CHANGELOG.md b/CHANGELOG.md index ed20e28..aa13362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * raise Vips::Error if `copy_memory` fails [VitaliiLazebnyi] * `Source.new_from_memory` keeps a reference to the memory area [jeremy] +* release the GVL during `copy_memory` and `write_to_memory` [andrewfader] ## Version 2.3.0 (2025-12-10) diff --git a/lib/vips/image.rb b/lib/vips/image.rb index af2bd0f..814621e 100644 --- a/lib/vips/image.rb +++ b/lib/vips/image.rb @@ -12,7 +12,9 @@ module Vips attach_function :vips_image_new_matrix_from_array, [:int, :int, :pointer, :int], :pointer - attach_function :vips_image_copy_memory, [:pointer], :pointer + # these two evaluate the whole pipeline, so release the GVL while they run + attach_function :vips_image_copy_memory, [:pointer], :pointer, + blocking: true attach_function :vips_image_set_progress, [:pointer, :int], :void attach_function :vips_image_set_kill, [:pointer, :int], :void @@ -31,7 +33,7 @@ module Vips end attach_function :vips_image_write_to_memory, - [:pointer, SizeStruct.ptr], :pointer + [:pointer, SizeStruct.ptr], :pointer, blocking: true attach_function :vips_image_get_typeof, [:pointer, :string], :GType attach_function :vips_image_get, diff --git a/spec/image_spec.rb b/spec/image_spec.rb index f1eeff7..2cb0f4e 100644 --- a/spec/image_spec.rb +++ b/spec/image_spec.rb @@ -94,6 +94,36 @@ expect { img.copy_memory }.to raise_exception(Vips::Error) end + # copy_memory and write_to_memory evaluate the whole pipeline, so they must + # release the GVL, or every other Ruby thread stalls until they return + def ticks_during + ticks = 0 + running = true + ticker = Thread.new do + while running + ticks += 1 + sleep 0.001 + end + end + sleep 0.01 until ticks > 0 + before = ticks + yield + during = ticks - before + running = false + ticker.join + during + end + + it "lets other threads run during copy_memory" do + image = Vips::Image.gaussnoise(2000, 2000).gaussblur(4) + expect(ticks_during { image.copy_memory }).to be > 0 + end + + it "lets other threads run during write_to_memory" do + image = Vips::Image.gaussnoise(2000, 2000).gaussblur(4) + expect(ticks_during { image.write_to_memory }).to be > 0 + end + it "throws an error when trying to load an image from memory with unknown size" do data = FFI::Pointer.new(1) expect { Vips::Image.new_from_memory(data, 16, 16, 1, :uchar) }.to raise_error(Vips::Error)