Thanks to visit codestin.com
Credit goes to github.com

Skip to content

undefined method 'new_from_file' for Vips::Image:Class #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
tomasc opened this issue Apr 11, 2017 · 46 comments · Fixed by #114
Closed

undefined method 'new_from_file' for Vips::Image:Class #107

tomasc opened this issue Apr 11, 2017 · 46 comments · Fixed by #114

Comments

@tomasc
Copy link

tomasc commented Apr 11, 2017

I am getting errors such as undefined method 'new_from_file' for Vips::Image:Class or uninitialized constant Vips::Image from here, seemingly randomly:

https://github.com/tomasc/dragonfly_libvips/blob/master/lib/dragonfly_libvips/processors/thumb.rb#L24

vips is required, I have vips-8.5.2 installed (Ubuntu 14), with gobject-introspection (3.1.1), ruby-vips (1.0.4)

Can't seem to track down the source of the issue … any ideas please?

Thanks,
Tomas

@tomasc tomasc changed the title undefined method `new_from_file' for Vips::Image:Class undefined method 'new_from_file' for Vips::Image:Class Apr 11, 2017
@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

You mean sometimes it works, sometimes not?

#98 sounds a little similar. That was (I think) to do with the way the vips gem handles startup. Maybe try adding something to trigger initialization earlier? You could try adding

x = Vips::Image.black 1, 1

Just after the require "vips".

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

Thank John, yes looks a bit like #98. I will try your suggestion and report back.

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

On another point, have you looked at the thumbnail method added in 8.5? It should be a lot faster than the resize you are using now.

http://jcupitt.github.io/libvips/API/current/libvips-resample.html#vips-thumbnail

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

For example, shrink a 9400 x 9400 pixel jpg down to 94 pixels across:

$ time vips resize wtc.jpg x.jpg 0.01
real	0m1.732s
user	0m1.708s
sys	0m0.020s
$ time vips thumbnail wtc.jpg x.jpg 94
real	0m0.326s
user	0m0.320s
sys	0m0.000s

thumbnail also supports lots of useful image shrink features, like auto-rotate and smartcrop.

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

great, will update to that!

BTW I moved the require in the method close to where the class is used and it does seem to stop the errors (https://github.com/tomasc/dragonfly_libvips/blob/master/lib/dragonfly_libvips/processors/thumb.rb#L23) – but let me give it more time before we close this

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

BTW2 @jcupitt – this is a feature of 8.5.x vips, correct? If so, is there going to be updated OS X homebrew formula? (latest seem to be 8.4.5)

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

Yes, there's an updated formula in the queue for approval, they've just not merged it yet:

https://github.com/Homebrew/homebrew-science/pull/5399

The CI is failing, but I think that's not vips.rb's fault, it looks like pycairo is broken at the moment.

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

I see, thank you!

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

Is there a performance hit from having the require there? As long as vips is only loaded once, sure!

Did you try initing vips on require (put some not-useful call to vips in just after the require)?

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

I believe subsequent calls to require in Ruby should have no effect as long as the file is already loaded.

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

BTW3 that speed gain of thumbnail looks awesome!

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

... the way vips init works, not much happens when the require runs, it just loads the typelib and adds some hooks. The first time you try to access a vips constant or method, the introspection fires and the rest of the binding is built.

You can see the logic here:

https://github.com/jcupitt/ruby-vips/blob/master/lib/vips.rb#L44

So ... if you require, then freeze the ruby process, then fork() for each request (at least rails does this, don't know about dragonfly) you will trigger the init as part of each request process, and perhaps have terrible confusion and breakage.

If you init after require, vips will be loaded up and ready to go when the request comes in, and can never be run from more than one context.

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

The thumbnail speed gain mostly comes from it exploiting shrink on load features in the supported libraries. You'll see even more for PDF or SVG, but little improvement for TIFF or PNG.

JPG gives a very nice speedup, and it's probably the most common case, so that's great.

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

when I do

require 'vips'
Vips::Image.black 1, 1

at the top of the file I get

GLib-CRITICAL **: g_hash_table_lookup: assertion 'hash_table != NULL' failed
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/gobject-introspection-3.1.1/lib/gobject-introspection/loader.rb:110:in `block in define_singleton_method'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/call.rb:173:in `build'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/call.rb:249:in `invoke'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/call.rb:278:in `call_base'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/image.rb:452:in `new_from_file'

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

speed up on PDF resize will be very welcomed!

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

Oh, weird, I wonder why that is. I guess if you make a file called x.rb:

#!/usr/bin/env ruby

require 'vips'
Vips::Image.black 1,1

Then:

$ ./x.rb

works OK?

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

it does …

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

Could you try this in dragonfly:

require 'vips'
Vips::set_debug TRUE
Vips::Image.black 1,1

It'll make the gem output some logging stuff as it starts up. You should see the missing const triggering vips_init, and that should make the hash table.

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

yes:

Vips::const_missing: Image
Vips::init: []
Vips::Loader.initialize: Vips, []
Vips::Loader.pre_load: #<GObjectIntrospection::Repository:0x007f9a5f936e48>, Vips
Vips::Loader.call_init_function: #<GObjectIntrospection::Repository:0x007f9a5f936e48>, Vips
Vips::Loader.post_load:
Vips::Call.init
name = black
supplied_values are:
   1
   1
optional_values are:
setting string options  ...
searching for first image argument ...
finding unassigned required input arguments ...
finding optional unassigned input arguments ...
finding optional output arguments ...
setting required input arguments ...
setting optional input args ...
building ...
cache miss ... building
adding to cache ... 
fetching outputs ...
fetching required output out
unreffing outputs ...
success! black.out = #<Vips::Image:0x007f9a5f8511e0>
Run options: --seed 42781

# Running:

...Vips::Call.init
name = VipsForeignLoadPng
supplied_values are:
   /Users/tomascelizna/Devel/dragonfly_libvips/samples/beach.png
optional_values are:
   ["access", "sequential"]
setting string options  ...
searching for first image argument ...
finding unassigned required input arguments ...
finding optional unassigned input arguments ...
finding optional output arguments ...
setting required input arguments ...
setting optional input args ...
setting access to sequential
building ...
GLib-CRITICAL **: g_hash_table_lookup: assertion 'hash_table != NULL' failed
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/gobject-introspection-3.1.1/lib/gobject-introspection/loader.rb:110:in `block in define_singleton_method'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/call.rb:173:in `build'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/call.rb:249:in `invoke'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/call.rb:278:in `call_base'
	from /Users/tomascelizna/.rbenv/versions/2.3.3/lib/ruby/gems/2.3.0/gems/ruby-vips-1.0.4/lib/vips/image.rb:452:in `new_from_file'
	from /Users/tomascelizna/Devel/dragonfly_libvips/lib/dragonfly_libvips/processors/encode.rb:19:in `call'

@jcupitt
Copy link
Member

jcupitt commented Apr 12, 2017

Ah OK, so it's initing correctly, but then I think forking before each request, so the child is not inited.

You're right, move the require to just before the first use. You'll be starting up vips for each request, but perhaps that's unavoidable :( It only adds 20ms or so, so perhaps it's not too bad.

@tomasc
Copy link
Author

tomasc commented Apr 12, 2017

Hmm … too bad.
Well at least in this case the results of the dragonfly operations are cached, so it should not be a big deal.
If there's any way I can help to make the init of ruby-vips more robust please let me know.

@tomasc
Copy link
Author

tomasc commented Apr 13, 2017

@jcupitt sadly I am still seeing the same errors in production even after moving the require inside of the method. I will try to see if I can track the issue down, but have to admit I have not many ideas where to look …

Maybe we will need to switch back to perusing the command line vipsthumbnail for the time being.

@jcupitt
Copy link
Member

jcupitt commented Apr 14, 2017

Hi again, I had an idea last night. I wonder if Ruby could be unloading the libvips binary somehow, perhaps on GC? This looks a little like a problem we had in the php binding:

libvips/php-vips#26 (comment)

So the fix might be to add an extra library load during init to prevent unload.

Is there a simple way for me to run dragonfly in a harness here? If not, could you copy-paste some more of your log?

We need to see how much of ruby-vips init is repeated the second time it loads, and verify that it is trying to reinit within the same process. Could you print the pid just before require "vips", and also paste the complete init output? So in your Thumb handler you have:

        puts "pid = #{Process.pid}"
        require 'vips'
        Vips::set_debug TRUE
        img = ::Vips::Image.new_from_file(content.path, input_options)

So we see this first time:

pid = 13414
Vips::const_missing: Image
Vips::init: []
Vips::Loader.initialize: Vips, []
Vips::Loader.pre_load: #<GObjectIntrospection::Repository:0x007f9a5f936e48>, Vips
Vips::Loader.call_init_function: #<GObjectIntrospection::Repository:0x007f9a5f936e48>, Vips
Vips::Loader.post_load:
Vips::Call.init
name = VipsForeignLoadPng
...

What is the output the second time through when it fails?

@felixbuenemann, this possibly sounds like your bug too, do you know if Ruby can unload libraries on GC?

@tomasc
Copy link
Author

tomasc commented Apr 14, 2017

thanks for this @jcupitt, I will plug this in and get back to you with logs

@tomasc
Copy link
Author

tomasc commented Apr 14, 2017

@jcupitt I think the Vips::set_debug TRUE causes (by logging the file's data) an error upstream (Encoding::UndefinedConversionError ("\xFF" from ASCII-8BIT to UTF-8)) so I never actually get to see the pid … any ideas?

It's at the end of the output of the debug:

…
fetching outputs ...
fetching required output buffer
unreffing outputs ...
success! VipsForeignSaveJpegBuffer.out = ????C ……………………

@jcupitt
Copy link
Member

jcupitt commented Apr 14, 2017

Oh, maybe Process.pid.to_s? I didn't actually test it.

@tomasc
Copy link
Author

tomasc commented Apr 14, 2017

The Process.pid is ok, that works fine.
The issue is Vips::set_debug TRUE which dumps out content of the output buffer directly to the log. The Rails app (in production) then chokes on that and I don't event get to see the output of the Vips debug info. (It is fine in development mode though.)

@jcupitt
Copy link
Member

jcupitt commented Apr 14, 2017

Oh I see, sorry.

I guess change vips to log to a file instead. You just need to change the log method:

https://github.com/jcupitt/ruby-vips/blob/master/lib/vips.rb#L8

You're a better Ruby programmer than me, but I guess open /tmp/vips.log and do f.puts msg.

@felixbuenemann
Copy link
Contributor

felixbuenemann commented Apr 20, 2017

The encoding error means that the debug print is trying to write binary data to stdout, which is opened with utf-8 encoding and the conversion fails.

The ruby docs for the Encoding Class contain an explanation of ruby's internal and external encoding.

@jcupitt
Copy link
Member

jcupitt commented Jun 16, 2017

@tomasc can we close this?

@tomasc
Copy link
Author

tomasc commented Jun 16, 2017

@jcupitt this is till happening – quite often actually in both my websites using ruby-vips. I owe you debug log, will try to get it soon.

@jcupitt
Copy link
Member

jcupitt commented Jul 15, 2017

Hello again, could you try deleting this code from vips.rb:

https://github.com/jcupitt/ruby-vips/blob/master/lib/vips.rb#L142

It might be causing confusion with fork and Ruby, I read somewhere, and it's not really necessary.

@tomasc
Copy link
Author

tomasc commented Jul 15, 2017

Thanks!
I made a fork (https://github.com/tomasc/ruby-vips/blob/master/lib/vips.rb#L142), will test in production and report back.

@jcupitt
Copy link
Member

jcupitt commented Jul 15, 2017

I've almost got a new version of the gem working:

https://github.com/jcupitt/ruby-vips/tree/ffi-experiment

It's 2.0.0, based on ffi instead of gobject-introspection, so all these problems might go away.

It fails 47 of 50 tests right now, but it does seem to sort-of work. Hopefully it'll be ready for testing by the end of today.

@tomasc
Copy link
Author

tomasc commented Jul 15, 2017

whoa, impressive @jcupitt !

@felixbuenemann
Copy link
Contributor

@tomasc I don't think removing the at_exit will help. Do you by any chance use a threaded web server like puma? It could be your require "vips" in the middleware could be happening on different threads concurrently and I noticed init is not thread-safe:

q=Queue.new
Thread.new { require "vips"; r = Vips::Access::SEQUENTIAL rescue $!; q << r }
Thread.new { require "vips"; r = Vips::Image.new_from_array([0]) rescue $!; q << r }
q.pop
=> #<NoMethodError: undefined method `new_from_array' for Vips::Image:Class
q.pop
=> #<Vips::Access sequential>

@tomasc
Copy link
Author

tomasc commented Jul 15, 2017

@felixbuenemann yes I am on puma, I tried require vips in on_worker_boot but that did not help.

@felixbuenemann
Copy link
Contributor

The on_worker_boot hook is for forking, not threading, each forked worker will still have multiple threads, by default 16. Try running puma with one thread per worker (--threads 1:1).

@tomasc
Copy link
Author

tomasc commented Jul 15, 2017

I see, will try.
BTW what do you think is the solution to this in the long term?

@felixbuenemann
Copy link
Contributor

It might be as easy as taking a mutex around initialization. Should definitely be fixable in ruby-vips.

@tomasc
Copy link
Author

tomasc commented Jul 15, 2017

way beyond my knowledge of Ruby :-)

@felixbuenemann
Copy link
Contributor

The following code works without error:

s=Mutex.new
q=Queue.new
Thread.new { require "vips"; r = s.synchronize { Vips::Access::SEQUENTIAL rescue $! }; q << r }
Thread.new { require "vips"; r = s.synchronize { Vips::Image.new_from_array([0]) rescue $! }; q << r }
q.pop
=> #<Vips::Image:0x7f8b30caaf68 ptr=0x7f8b2f5c2000>
q.pop
=> #<Vips::Access sequential>

Of course this is not the actual solution, is only shows that synchronizing the init helps.

@felixbuenemann
Copy link
Contributor

felixbuenemann commented Jul 15, 2017

Here's a runnable test case for the bug:

require "vips"
10.times do
  fork do
    q=Queue.new
    th=[]
    th << Thread.new { q << (Vips::Access::SEQUENTIAL rescue $!) }
    th << Thread.new { q << (Vips::Image.new_from_array([0]) rescue $!) }
    th.map(&:join)
    puts q.pop.inspect
    puts q.pop.inspect
  end
end
Process.waitall

Note that the fork is unessential to the problem, I just need to run the code multiple without vips being already initialized.

The different exceptions I get are:

#<ArgumentError: Vips.init: wrong number of arguments (0 for 1)>
#<NoMethodError: undefined method `new_from_array' for Vips::Image:Class
#<NameError: uninitialized constant Vips::Image

This test case is very similar to what happens in puma cluster mode:

  • The puma webserver preloads the app at which time it requires the ruby-vips gem
  • The webserver forks multiple workers
  • Each worker starts a fixed or dynamic thread pool to handle requests
  • Some of those threads receive requests concurrently which trigger the vips init race condition

@jcupitt I'll try to come up with a fix unless you beat me to it ;-)

@jcupitt
Copy link
Member

jcupitt commented Jul 15, 2017

Wow yes, vips init certainly does not try to be threadsafe, this certainly needs fixing.

@felixbuenemann please go ahead. The experimental ffi ruby-vips 2.0 has the same top bits as ruby-vips, just a new backend, so your fix should copy over very simply.

@jcupitt
Copy link
Member

jcupitt commented Jul 15, 2017

The experimental new ruby-vips passes the test suite: #115

Testing very welcome!

@felixbuenemann
Copy link
Contributor

@tomasc The bug from this issue should be fixed in ruby-vips 1.0.6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants