Open Ishotihadus opened 1 year ago
I found that this phenomenon occurs when f.tag.item_map
is accessed after some tags are added.
(Of course, tag[key] = item
is counted as a tag addition.)
As a workaround, write item_map = f.tag.item_map
at first, and then use item_map.insert
or item_map.fetch
.
Calling f.tag.item_map
after inserting tags can cause a segmentation fault.
I suspect this is a problem in the implementation of __setItem__
.
I am clueless about C++ so I don't understand why, but in this code I had to take a pointer to the item list map before modifying it.
Edit: take this with a grain of salt, I have not reproduced the problem yet.
@Ishotihadus I am not able to reproduce the segfault. I tried the following, on 189fa6b.
bundle exec rake
ruby -Ilib -rtaglib test.rb
With test.rb being:
f = TagLib::MP4::File.new('test/data/mp4.m4a')
100.times do
f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
end
puts f.tag.item_map.to_h
This prints a Ruby hash, as expected, without segfaulting.
Could you try this code? This always fails in my environment even without irb.
# frozen_string_literal: true
require 'net/http'
require 'json'
100.times do
f = TagLib::MP4::File.new('test.m4a')
f.tag['©nam'] = TagLib::MP4::Item.from_string_list(['name'])
f.tag['©ART'] = TagLib::MP4::Item.from_string_list(['artist'])
request = Net::HTTP::Post.new(URI.parse('https://example.com'))
request.content_type = 'application/x-www-form-urlencoded;charset=UTF-8'
request.set_form_data('name' => f.tag['©nam'].to_string_list[0], 'artist' => f.tag['©ART'].to_string_list[0])
Net::HTTP.start('example.com', 443, use_ssl: true) { |http| http.request(request) }
f.tag['©lyr'] = TagLib::MP4::Item.from_string_list(['a'])
end
I have a feeling that something in the memory handling around strings is causing this problem, but I am not sure.
@Ishotihadus that reproducer still does nothing on my end. I'm also using a mac and taglib 1.13.
My environment has
My Gemflie.lock
is: Gemfile.lock.
I also attach logs of bundle exec rake
: rake.log; but I think there is no valuable information in this log.
Thanks for your supporting.
I found this problem appears on Ruby >= 3.2.0 and does not appear on Ruby <= 3.1.4! I use Ruby 3.1.4 for taglib-ruby for now!
Thanks. Using ruby 3.2.2 I also get the segfault.
I can't get rid of the HTTP request in the reproducer yet; writing the tag values to /dev/null
breaks the reproducer.
The segfault also happens if I replace f.tag['©lyr'] = ...
with f.tag.empty?
. Looks like f.tag
is a Ruby object pointing to a garbage memory address?
I've narrowed down the reproducer a bit.
require 'net/http'
def do_stuff
Net::HTTP.get('localhost', '/', 8080)
end
$stdout.sync=true
1000.times do |i|
printf("%d ", i)
f = TagLib::MP4::File.new('test/data/mp4.m4a')
f.tag['©ART'] = TagLib::MP4::Item.from_string_list(['artist'])
do_stuff
f.tag.empty?
f.close
end
puts
I still don't understand why I can't get rid of net/http
here.
The number of iterations before a segfault seems to be kind of stable. It varies if you make small changes to the code, such as adding another f.tag[xxx] =
.
Interesting. What might help narrowing it down is to see if any of the specific TagLib functionality triggers it. E.g. if you remove from_string_list
and use a different tag, can you still reproduce? If not, that would suggest the problem is somewhere in our handling of string lists (just an example).
Also, reading through the Ruby 3.2 release notes, it looks like YJIT is now enabled by default? Does the problem also occur if you disable it on 3.2 (not sure if that's possible?) or if you enable it on < 3.2?
Good idea, but YJIT does not seem to be active when I reproduce the bug. I checked by adding puts "YJIT enabled: #{RubyVM::YJIT.enabled?}"
which reports false
.
Replacing from_string_list
with e.g. from_int
does not make the crash go away. Replacing the f.tag[xxx] =
with f.tag.empty?
still triggers the crash.
require 'net/http'
def do_stuff
Net::HTTP.get('localhost', '/', 8080)
end
$stdout.sync=true
puts "YJIT enabled: #{RubyVM::YJIT.enabled?}"
1000.times do
f = TagLib::MP4::File.new('test/data/mp4.m4a')
f.tag.empty?
do_stuff
f.tag.empty? # <- second call to #empty? triggers segfault
f.close
end
puts
To be clear, it's not about #empty?
, that is just a conveniently succise method call. It also crashes if you call #album
or something else.
It seems as if the HTTP request eventually causes the tag to get deallocated somewhere (in C++?).
TagLib::MP4::Tag#[]=
sometimes fails even if the same actions often success.