rails-sqlserver / tiny_tds

TinyTDS - Simple and fast FreeTDS bindings for Ruby using DB-Library.
Other
607 stars 189 forks source link

Segementation Fault when reading from a closed connection #435

Open ylansegal opened 5 years ago

ylansegal commented 5 years ago

Thank you for your open source contribution and making this library available. Your work is appreciated!

Environment

Operating System

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.4
BuildVersion:   18E226

TinyTDS Version and Information

$ tsql -C
[TinyTds][v1.0.5][tsql]: /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-1.0.5/exe/tsql
Compile-time settings (established with the "configure" script)
                            Version: freetds v1.00.15
             freetds.conf directory: /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-1.0.5/ports/x86_64-apple-darwin18.2.0/etc
     MS db-lib source compatibility: no
        Sybase binary compatibility: no
                      Thread safety: yes
                      iconv library: yes
                        TDS version: 7.3
                              iODBC: no
                           unixodbc: no
              SSPI "trusted" logins: no
                           Kerberos: no
                            OpenSSL: yes
                             GnuTLS: no
                               MARS: no

FreeTDS Version

$ brew info freetds
freetds: stable 1.1.6 (bottled), HEAD

Description

Reading TinyTds::Results tied to a connection that is closed results in a segmentation fault most of the time, instead of the expected exception.

# repro.rb
require 'tiny_tds'

opts = { host: 'server.com', port: 1433, username: '***', password: '***' }
client = TinyTds::Client.new(opts)
results = client.execute('SELECT 42 as answer_to_life')

client.close
puts results.to_a

My expectation, is that the above code would raise a TinyTds::Error. When I run this multiple times, I see this happens rarely. The most common outcome is a Segmentation Fault:

$ $ for i in {1..20}; do ruby repro.rb 2>&1 | head -n 1; done
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000

An example of the segmentation fault details:

$ ruby repro.rb
repro.rb:8: [BUG] Segmentation fault at 0x0000000000000000
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]

-- Crash Report log information --------------------------------------------
   See Crash Report log file under the one of following:
     * ~/Library/Logs/DiagnosticReports
     * /Library/Logs/DiagnosticReports
   for more details.
Don't forget to include the above Crash Report log file in bug reports.

-- Control frame information -----------------------------------------------
c:0004 p:---- s:0017 e:000016 CFUNC  :each
c:0003 p:---- s:0014 E:000ea0 CFUNC  :to_a
c:0002 p:0064 s:0010 E:002318 EVAL   repro.rb:8 [FINISH]
c:0001 p:0000 s:0003 E:001580 (none) [FINISH]

-- Ruby level backtrace information ----------------------------------------
repro.rb:8:in `<main>'
repro.rb:8:in `to_a'
repro.rb:8:in `each'

-- Machine register context ------------------------------------------------
 rax: 0x0000000000000001 rbx: 0x00007f836de01270 rcx: 0x0000000000000001
 rdx: 0x0000000000000000 rdi: 0x00007f836dd9cfa0 rsi: 0x0000000000000000
 rbp: 0x00007ffee3c3ea80 rsp: 0x00007ffee3c3ea40  r8: 0x00000000004b4718
  r9: 0xffffffff00000000 r10: 0x00007f836de00ea8 r11: 0x0000000000000246
 r12: 0x00007f836de016d8 r13: 0x0000000000000000 r14: 0x00007f836dd9cfa0
 r15: 0x6000000000000000 rip: 0x000000010c659fdc rfl: 0x0000000000010206

-- C level backtrace information -------------------------------------------
0   ruby                                0x000000010c1c0077 rb_vm_bugreport + 135
1   ruby                                0x000000010c036873 rb_bug_context + 467
2   ruby                                0x000000010c12e1f1 sigsegv + 81
3   libsystem_platform.dylib            0x00007fff64606b5d _sigtramp + 29
4   libsybdb.5.dylib                    0x000000010c659fdc dbsqlok + 46
5   ruby                                0x000000010c1694d2 rb_thread_call_without_gvl + 82
6   tiny_tds.bundle                     0x000000010c64daa9 rb_tinytds_result_ok_helper + 89
7   tiny_tds.bundle                     0x000000010c64cdaa rb_tinytds_result_each + 458
8   ruby                                0x000000010c1ba07a vm_call0_body + 554
9   ruby                                0x000000010c1a7ae7 rb_call + 247
10  ruby                                0x000000010c1a88ae rb_iterate0 + 206
11  ruby                                0x000000010c1a89bc rb_block_call + 76
12  ruby                                0x000000010c0295d9 enum_to_a + 57
13  ruby                                0x000000010c1b2b67 vm_call_cfunc + 295
14  ruby                                0x000000010c19aca3 vm_exec_core + 12419
15  ruby                                0x000000010c1ad1f0 vm_exec + 144
16  ruby                                0x000000010c040931 ruby_exec_internal + 177
17  ruby                                0x000000010c040828 ruby_run_node + 56
18  ruby                                0x000000010bfc0a7f main + 79

-- Other runtime information -----------------------------------------------

* Loaded script: repro.rb

* Loaded features:

    0 enumerator.so
    1 thread.rb
    2 rational.so
    3 complex.so
    4 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/x86_64-darwin18/enc/encdb.bundle
    5 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/x86_64-darwin18/enc/trans/transdb.bundle
    6 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/x86_64-darwin18/rbconfig.rb
    7 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/compatibility.rb
    8 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/defaults.rb
    9 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/deprecate.rb
   10 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/errors.rb
   11 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/version.rb
   12 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/requirement.rb
   13 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/platform.rb
   14 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/basic_specification.rb
   15 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/stub_specification.rb
   16 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/delegate.rb
   17 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/rfc2396_parser.rb
   18 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/rfc3986_parser.rb
   19 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/common.rb
   20 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/generic.rb
   21 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/ftp.rb
   22 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/http.rb
   23 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/https.rb
   24 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/ldap.rb
   25 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/ldaps.rb
   26 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri/mailto.rb
   27 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/uri.rb
   28 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/specification_policy.rb
   29 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/util/list.rb
   30 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/x86_64-darwin18/stringio.bundle
   31 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/specification.rb
   32 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/exceptions.rb
   33 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/util.rb
   34 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/bundler_version_finder.rb
   35 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/dependency.rb
   36 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_gem.rb
   37 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/monitor.rb
   38 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb
   39 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_warn.rb
   40 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems.rb
   41 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/site_ruby/2.5.0/rubygems/path_support.rb
   42 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/version.rb
   43 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/core_ext/name_error.rb
   44 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/levenshtein.rb
   45 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/jaro_winkler.rb
   46 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/spell_checker.rb
   47 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb
   48 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb
   49 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/spell_checkers/name_error_checkers.rb
   50 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/spell_checkers/method_name_checker.rb
   51 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/spell_checkers/key_error_checker.rb
   52 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/spell_checkers/null_checker.rb
   53 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean/formatters/plain_formatter.rb
   54 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/did_you_mean-1.2.0/lib/did_you_mean.rb
   55 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/x86_64-darwin18/date_core.bundle
   56 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/date.rb
   57 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/2.5.0/x86_64-darwin18/bigdecimal.bundle
   58 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-2.1.2/lib/tiny_tds/version.rb
   59 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-2.1.2/lib/tiny_tds/error.rb
   60 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-2.1.2/lib/tiny_tds/client.rb
   61 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-2.1.2/lib/tiny_tds/result.rb
   62 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-2.1.2/lib/tiny_tds/gem.rb
   63 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-2.1.2/lib/tiny_tds/tiny_tds.bundle
   64 /Users/ylans/.asdf/installs/ruby/2.5.3/lib/ruby/gems/2.5.0/gems/tiny_tds-2.1.2/lib/tiny_tds.rb

[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html

[IMPORTANT]
Don't forget to include the Crash Report log file under
DiagnosticReports directory in bug reports.

For what it's worth, I can work around the issue by reading all of the results into ruby before closing the connection:

# workaround.rb
require 'tiny_tds'

opts = { host: 'server.com', port: 1433, username: '***', password: '***' }
client = TinyTds::Client.new(opts)
# Notice the "each" called on `TinyTds::Result`
results = client.execute('SELECT 42 as answer_to_life').each

client.close
puts results.to_a
$ for i in {1..20}; do ruby workaround.rb 2>&1 | head -n 1; done
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}
{"answer_to_life"=>42}

Please do let me know if I can provide more information or context.

ylansegal commented 5 years ago

In addition, I can also reproduce -- to a certain extent -- when the garbage collector runs, even if the connection is not closed explicitly:

# repro.rb

require 'tiny_tds'

class Hitchhiker
  def execute
    opts = { host: 'server.com', port: 1433, username: '***', password: '***' }
    TinyTds::Client.new(opts).execute('SELECT 42 as answer_to_life')
  end
end

dent = Hitchhiker.new
results = dent.execute
GC.start

puts results.to_a
$ for i in {1..20}; do ruby repro.rb 2>&1 | head -n 1; done
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15:in `each': DBPROCESS is dead or not enabled (TinyTds::Error)
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000
repro.rb:15: [BUG] Segmentation fault at 0x0000000000000000