From 7843842d2122902869db4f6ad8e85925d94b7880 Mon Sep 17 00:00:00 2001 From: Hiroshi Nakamura Date: Thu, 2 Dec 2010 23:43:38 +0900 Subject: [PATCH] Proof of concept fix for JRUBY-5200: configureBlocking free select. RubyThread.select for both sysread and syswrite is a source of deadlock when 2 operations are running by different Thread. RubyThread.select try to synchronize SelectableChannel.blockingLock first so it cannot be used for different SelectionKey.OP_* operations. This commit copies RubyThread.select to SSLSocket and remove blockingLock. This impl just set SelectableChannel.configureBlocking(false) permanently instead of setting temporarily. SSLSocket requires wrapping IO to be selectable so it should be OK to set configureBlocking(false) permanently I think... --- src/java/org/jruby/ext/openssl/SSLSocket.java | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/java/org/jruby/ext/openssl/SSLSocket.java b/src/java/org/jruby/ext/openssl/SSLSocket.java index d194da0..70076dd 100644 --- a/src/java/org/jruby/ext/openssl/SSLSocket.java +++ b/src/java/org/jruby/ext/openssl/SSLSocket.java @@ -30,12 +30,15 @@ import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.util.Set; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -53,6 +56,7 @@ import org.jruby.RubyObject; import org.jruby.RubyObjectAdapter; import org.jruby.RubyString; +import org.jruby.RubyThread; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.ext.openssl.x509store.X509Utils; @@ -235,8 +239,71 @@ public IRubyObject verify_result() { } private void waitSelect(int operations) throws IOException { + if (!(io.getChannel() instanceof SelectableChannel)) { + return; + } + SelectableChannel selectable = (SelectableChannel)io.getChannel(); ThreadContext ctx = getRuntime().getCurrentContext(); - ctx.getThread().select(io, operations); + RubyThread thread = ctx.getThread(); + + selectable.configureBlocking(false); + SelectionKey key = null; + Selector currentSelector = null; + try { + selectable.configureBlocking(false); + + io.addBlockingThread(thread); + currentSelector = getRuntime().getSelectorPool().get(); + + key = selectable.register(currentSelector, operations); + + ctx.getThread().beforeBlockingCall(); + int result = currentSelector.select(); + + // check for thread events, in case we've been woken up to die + ctx.getThread().pollThreadEvents(); + + if (result == 1) { + Set keySet = currentSelector.selectedKeys(); + + if (keySet.iterator().next() == key) { + return; + } + } + } catch (IOException ioe) { + throw getRuntime().newRuntimeError("Error with selector: " + ioe); + } finally { + // Note: I don't like ignoring these exceptions, but it's + // unclear how likely they are to happen or what damage we + // might do by ignoring them. Note that the pieces are separate + // so that we can ensure one failing does not affect the others + // running. + + // clean up the key in the selector + try { + if (key != null) key.cancel(); + if (currentSelector != null) currentSelector.selectNow(); + } catch (Exception e) { + // ignore + } + + // shut down and null out the selector + try { + if (currentSelector != null) { + getRuntime().getSelectorPool().put(currentSelector); + } + } catch (Exception e) { + // ignore + } finally { + currentSelector = null; + } + + // remove this thread as a blocker against the given IO + io.removeBlockingThread(thread); + + // clear thread state from blocking call + ctx.getThread().afterBlockingCall(); + } } private void doHandshake() throws IOException {