Segmentation fault after killing thread (ruby 2.5.1, curb 0.9.6, ubuntu 16.04)
RGBD opened this issue · 8 comments
See repo
Environment
- OS: Ubuntu 16.04
- RVM
- Ruby: 2.2.3, 2.3.6, 2.4.2, 2.5.1,
- Curb: 0.8.8, 0.9.6,
- OpenSSL 1.0.2g,
- Curl: 7.47.0
What happens
Killing a thread during Curb::Easy#perform causes segfault. Timeout does that under the hood.
Sometimes it happens right after the kill, sometimes on the next garbage collection.
Steps to reproduce
Install ruby 2.5.1 through rvm.
Start proxy.sh
Start curb_fail.rb
quick notes...
Timeout.timeout(RUBY_TIMEOUT) do
easy.perform
end
Can be and should be replaced with
easy.dns_cache_timeout = 3
easy.connect_timeout = 8
easy.timeout = 18
easy.perform
your timeouts can be whatever you need but those would be the three timeouts that can cause things to block... Timeout in ruby spawns a thread meaning your easy handle has memory that is being shared between two different threads usually posix threads... e.g. not good idea..
Testing on master and with ruby 2.5.3
bundle exec ruby curb_fail.rb
NOTE: Gem::Specification#has_rdoc= is deprecated with no replacement. It will be removed on or after 2018-12-01.
Gem::Specification#has_rdoc= called from /Users/taf2/work/curb/curb.gemspec:21.
RUBY_VERSION: 2.5.3
Curl::CURB_VERSION: 0.9.6
OpenSSL::OPENSSL_VERSION: OpenSSL 1.0.2p 14 Aug 2018
TARGET_URL: https://ietf.org
PROXY_URL: 127.0.0.1:8888
RUBY_TIMEOUT: 0.1
0
Rebuilt URL to: https://ietf.org/
0
Trying 127.0.0.1...
0
TCP_NODELAY set
0
Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
0
allocate connect buffer!
0
Establish HTTP proxy tunnel to ietf.org:443
2
CONNECT ietf.org:443 HTTP/1.1
Host: ietf.org:443
Proxy-Connection: Keep-Alive
Traceback (most recent call last):
1: from curb_fail.rb:30:in `<main>'
curb_fail.rb:34:in `rescue in <main>': undefined method `ap' for main:Object (NoMethodError)
ap is undefined because the timeout error is raised. again you should not use Timeout in general in ruby... it's a thread curb has builtin timeout handling that is more reliable and doesn't spawn threads.
I'm thinking the segfault you are receiving is based on the perform method being run in the timeout handler and allocating objects in one thread and then in another thread objects are deallocated... or this has been fixed in master.
Okay on ruby 2.5.3 and master I had to modify the example and run it a few hundred times but have a failure now with freeing already freed memory
#<Timeout::Error: execution expired>
close
ruby(94475,0x10e3a25c0) malloc: *** error for object 0x7ffeeae60ba0: pointer being freed was not allocated
ruby(94475,0x10e3a25c0) malloc: *** set a breakpoint in malloc_error_break to debug
Abort trap: 6
I was only able to reproduce it in ubuntu 16.04 with 99% success rate. I know that timeout is a bad idea here, but that's a minimal example of what I've encountered in production code.
Throwing time out away was the solution)
You can use the timeout_ms as well if you need a higher resolution timeout
Also I am sure there is more we can do here https://curl.haxx.se/libcurl/c/threadsafe.html
Either having libcurl use ruby memory allocators or setting up a mutex to work with ruby better.
As it stands this is a won't fix issue. You should use a timeout on the http connection object provided by curl and avoid ruby Timeout because it's using threads. Or if you have to use Timeout then keep the scope of the curl handle within the whole Timeout block.