RubyThread.java
/*
* Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.runtime.core;
import org.jruby.RubyThread.Status;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.objects.Allocator;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.control.ReturnException;
import org.jruby.truffle.runtime.control.ThreadExitException;
import org.jruby.truffle.runtime.subsystems.ThreadManager;
import java.util.concurrent.CountDownLatch;
/**
* Represents the Ruby {@code Thread} class. Implemented using Java threads, but note that there is
* not a one-to-one mapping between Ruby threads and Java threads - specifically in combination with
* fibers as they are currently implemented as their own Java threads.
*/
public class RubyThread extends RubyBasicObject {
public Object getValue() {
return value;
}
public RubyException getException() {
return exception;
}
public void shutdown() {
}
private final ThreadManager manager;
private final CountDownLatch finished = new CountDownLatch(1);
private Status status = Status.RUN;
private RubyException exception;
private Object value;
private RubyBasicObject threadLocals;
public RubyThread(RubyClass rubyClass, ThreadManager manager) {
super(rubyClass);
this.manager = manager;
threadLocals = new RubyBasicObject(rubyClass.getContext().getCoreLibrary().getObjectClass());
}
public void initialize(RubyContext context, RubyNode currentNode, RubyProc block) {
final RubyProc finalBlock = block;
initialize(context, currentNode, new Runnable() {
@Override
public void run() {
value = finalBlock.rootCall();
}
});
}
public void initialize(final RubyContext context, final RubyNode currentNode, Runnable runnable) {
final RubyThread finalThread = this;
final Runnable finalRunnable = runnable;
new Thread(new Runnable() {
@Override
public void run() {
finalThread.manager.registerThread(finalThread);
finalThread.manager.enterGlobalLock(finalThread);
context.getSafepointManager().enterThread();
try {
finalRunnable.run();
} catch (ThreadExitException e) {
return;
} catch (RaiseException e) {
exception = e.getRubyException();
} catch (ReturnException e) {
exception = getContext().getCoreLibrary().unexpectedReturn(currentNode);
} finally {
finalThread.manager.leaveGlobalLock();
finalThread.manager.unregisterThread(finalThread);
finalThread.finished.countDown();
context.getSafepointManager().leaveThread();
status = Status.DEAD;
}
}
}).start();
}
public void join() {
final RubyThread runningThread = getContext().getThreadManager().leaveGlobalLock();
try {
while (true) {
try {
finished.await();
break;
} catch (InterruptedException e) {
// Await again
}
}
} finally {
getContext().getThreadManager().enterGlobalLock(runningThread);
}
if (exception != null) {
throw new RaiseException(exception);
}
}
public Status getStatus() {
return status;
}
public RubyBasicObject getThreadLocals() {
return threadLocals;
}
public static class ThreadAllocator implements Allocator {
@Override
public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, RubyNode currentNode) {
return new RubyThread(rubyClass, context.getThreadManager());
}
}
}