Permalink
Browse files

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...
  • Loading branch information...
1 parent 7b20721 commit 4fd558e27df6fb1a6019bc9322d96361e5ae536a @nahi nahi committed Dec 2, 2010
Showing with 68 additions and 1 deletion.
  1. +68 −1 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<SelectionKey> 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 {

0 comments on commit 4fd558e

Please sign in to comment.