kubo / ruby-oci8

Ruby-oci8 - Oracle interface for ruby
Other
169 stars 75 forks source link

`__initialize': executing in another thread (RuntimeError) #74

Closed javornikolov closed 9 years ago

javornikolov commented 9 years ago

Concurrent use of oci8 connection in multiple threads raises error:

ruby-2.2.2/gems/ruby-oci8-2.1.8/lib/oci8/cursor.rb:28:in `__initialize': executing in another thread (RuntimeError)
    from /usr/local/rvm/gems/ruby-2.2.2/gems/ruby-oci8-2.1.8/lib/oci8/cursor.rb:28:in `initialize'
    from /usr/local/rvm/gems/ruby-2.2.2/gems/ruby-oci8-2.1.8/lib/oci8/oci8.rb:177:in `new'
    from /usr/local/rvm/gems/ruby-2.2.2/gems/ruby-oci8-2.1.8/lib/oci8/oci8.rb:177:in `parse_internal'
    from /usr/local/rvm/gems/ruby-2.2.2/gems/ruby-oci8-2.1.8/lib/oci8/oci8.rb:170:in `parse'
    from ce.rb:6:in `db_work'
    from ce.rb:13:in `<main>'

ce.rb:

require 'oci8'

@conn = OCI8.new(ENV['DB_USER'], ENV['DB_PASSWORD'], ENV['DB_NAME'])

def db_work
  crs = @conn.parse "begin dbms_lock.sleep(5); end;"
  crs.exec
  crs.close
end

t1 = Thread.new { db_work }
sleep 1
db_work

@conn.logoff

It seems to fail even when connection instance in the 2nd thread is not the same:

require 'oci8'

conn1 = OCI8.new(ENV['DB_USER'], ENV['DB_PASSWORD'], ENV['DB_NAME'])
conn2 = OCI8.new(ENV['DB_USER'], ENV['DB_PASSWORD'], ENV['DB_NAME'])

def db_work(conn)
  crs = conn.parse "begin dbms_lock.sleep(5); end;"
  crs.exec
  crs.close
end

t1 = Thread.new { db_work(conn1) }
sleep 1
db_work(conn2)

[conn1, conn2].each { |c| c.logoff }
ruby-2.2.2/gems/ruby-oci8-2.1.8/lib/oci8/cursor.rb:129:in `__execute': Canceled by user request. (OCIBreak)
    from /usr/local/rvm/gems/ruby-2.2.2/gems/ruby-oci8-2.1.8/lib/oci8/cursor.rb:129:in `exec'
    from ce.rb:8:in `db_work'
    from ce.rb:14:in `<main>'
kubo commented 9 years ago

The first issue

Concurrent use of oci8 connection in multiple threads raises error:

It raises the error by intent. Otherwise, ruby process itself is terminated by segmentation fault.

The second issue

It seems to fail even when connection instance in the 2nd thread is not the same:

No. When a sub-thread exits and the main thread become only live thread, the main thread is interrupted and the SQL execution in the main thread is cancelled.

The minimum code to reproduce the second issue is:

require 'oci8'

conn = OCI8.new(ENV['DB_USER'], ENV['DB_PASSWORD'], ENV['DB_NAME'])

Thread.new { sleep 2 }
conn.exec "begin dbms_lock.sleep(5); end;"

The above code woks with ruby 1.9.3 or lower but it raises an error with ruby 2.0.0 or upper due to ruby/ruby@39d38ff82f3a6158d2adc5883b86691a424ed1c0.

I have no good idea to fix it without changing ruby itself.

javornikolov commented 9 years ago

Thank you for the feedback! It helps me think of two workarounds: (1) run SQL in non-main threads only; or (2) block child threads until the main thread signals them that it's a good enough moment to finish.

If there is a good way to fix that in Ruby itself, maybe that's the way to go. I suspect other ruby wrappers around native libraries could be affected in similar way.

kubo commented 9 years ago

I made a patch ruby/ruby#898 of ruby and it was accepted. The second issue will be fixed by the next ruby release.

javornikolov commented 9 years ago

Awesome! Thank you very much :-)

kubo commented 9 years ago

FYI. The patch ruby/ruby#898 was applied to the ruby trunk. It will be included in ruby 2.3.0. However it has not been backported. The latest patch releases of ruby 2.0, 2.1 and 2.2 have same issue.