taf2/curb

Segmentation fault after killing thread (ruby 2.5.1, curb 0.9.6, ubuntu 16.04)

RGBD opened this issue · 8 comments

RGBD commented

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

taf2 commented

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.

taf2 commented

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
RGBD commented

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)

taf2 commented

You can use the timeout_ms as well if you need a higher resolution timeout

taf2 commented

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.

taf2 commented

I believe 01b715a fixes this issue. Thanks to @robuye

taf2 commented

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.