Skip to content

Commit

Permalink
[Truffle] Fix the stack server.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisseaton committed Feb 27, 2015
1 parent 5187aac commit 1440adc
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 99 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/util/cli/Options.java
Expand Up @@ -140,7 +140,7 @@ public class Options {
public static final Option<Boolean> TRUFFLE_LOAD_CORE = bool(TRUFFLE, "truffle.load_core", true, "Load the Truffle core library.");

public static final Option<Integer> TRUFFLE_PASSALOT = integer(TRUFFLE, "truffle.passalot", 0, "Probabilty between 0 and 100 to randomly insert Thread.pass at a given line.");
public static final Option<Integer> TRUFFLE_STACK_SERVER_PORT = integer(TRUFFLE, "truffle.stack_server_port", 0, "Port number to run an HTTP server on that returns stack traces");
public static final Option<Integer> TRUFFLE_INSTRUMENTATION_SERVER_PORT = integer(TRUFFLE, "truffle.instrumentation_server_port", 0, "Port number to run an HTTP server on that provides instrumentation services");
public static final Option<String> TRUFFLE_TRANSLATOR_PRINT_AST = string(TRUFFLE, "truffle.translator.print_asts", "", "Comma delimited list of method names to print the AST of after translation.");
public static final Option<String> TRUFFLE_TRANSLATOR_PRINT_FULL_AST = string(TRUFFLE, "truffle.translator.print_full_asts", "", "Comma delimited list of method names to print the full AST of after translation.");
public static final Option<String> TRUFFLE_TRANSLATOR_PRINT_PARSE_TREE = string(TRUFFLE, "truffle.translator.print_parse_trees", "", "Comma delimited list of method names to print the JRuby parse tree of before translation.");
Expand Down
Expand Up @@ -54,12 +54,14 @@ public static InternalMethod getMethod(FrameInstance frame) {
return RubyArguments.getMethod(frame.getFrame(FrameInstance.FrameAccess.READ_ONLY, true).getArguments());
}

private static final boolean BACKTRACE_GENERATE = Options.TRUFFLE_BACKTRACE_GENERATE.load();

public static Backtrace getBacktrace(Node currentNode) {
CompilerAsserts.neverPartOfCompilation();

final ArrayList<Activation> activations = new ArrayList<>();

if (Options.TRUFFLE_BACKTRACE_GENERATE.load()) {
if (BACKTRACE_GENERATE) {
/*
* TODO(cs): if this materializing the frames proves really expensive
* we might want to make it optional - I think it's only used for some
Expand Down
Expand Up @@ -111,8 +111,8 @@ public RubyContext(Ruby runtime) {

rubiniusPrimitiveManager = RubiniusPrimitiveManager.create();

if (Options.TRUFFLE_STACK_SERVER_PORT.load() != 0) {
new StackServerManager(this, Options.TRUFFLE_STACK_SERVER_PORT.load()).start();
if (Options.TRUFFLE_INSTRUMENTATION_SERVER_PORT.load() != 0) {
new InstrumentationServerManager(this, Options.TRUFFLE_INSTRUMENTATION_SERVER_PORT.load()).start();
}

runningOnWindows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).indexOf("win") >= 0;
Expand Down Expand Up @@ -240,7 +240,7 @@ public Object execute(Source source, Encoding defaultEncoding, TranslatorDriver.
final InternalMethod method = new InternalMethod(rootNode.getSharedMethodInfo(), rootNode.getSharedMethodInfo().getName(),
getCoreLibrary().getObjectClass(), Visibility.PUBLIC, false, callTarget, parentFrame);

return callTarget.call(RubyArguments.pack(method, parentFrame, self, null, new Object[] {}));
return callTarget.call(RubyArguments.pack(method, parentFrame, self, null, new Object[]{}));
}

public long getNextObjectID() {
Expand Down
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2015 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.subsystems;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import org.jruby.truffle.runtime.RubyCallStack;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.backtrace.Backtrace;
import org.jruby.truffle.runtime.core.RubyThread;
import org.jruby.truffle.runtime.util.Consumer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

@SuppressWarnings("restriction")
public class InstrumentationServerManager {

private final RubyContext context;
private final int port;

public InstrumentationServerManager(RubyContext context, int port) {
this.context = context;
this.port = port;
}

public void start() {
final HttpServer server;

try {
server = HttpServer.create(new InetSocketAddress(port), 0);
} catch (IOException e) {
e.printStackTrace();
return;
}

server.createContext("/stacks", new HttpHandler() {

@Override
public void handle(HttpExchange httpExchange) {
try {
final StringBuilder builder = new StringBuilder();

context.getSafepointManager().pauseAllThreadsAndExecuteFromNonRubyThread(new Consumer<RubyThread>() {

@Override
public void accept(RubyThread thread) {
try {
Backtrace backtrace = RubyCallStack.getBacktrace(null);

synchronized (this) {
// Not thread-safe so keep the formatting synchronized for now.
String[] lines = Backtrace.DISPLAY_FORMATTER.format(context, null, backtrace);

builder.append(Thread.currentThread().getName());
builder.append("\n");
for (String line : lines) {
builder.append(line);
builder.append("\n");
}
builder.append("\n");
}
} catch (Throwable e) {
e.printStackTrace();
}
}

});

final byte[] bytes = builder.toString().getBytes("UTF-8");

httpExchange.getResponseHeaders().set("Content-Type", "text/plain");
httpExchange.sendResponseHeaders(200, bytes.length);

final OutputStream stream = httpExchange.getResponseBody();
stream.write(bytes);
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

});

server.start();
}

}
Expand Up @@ -30,7 +30,7 @@ public class SafepointManager {

private final RubyContext context;

private final Set<Thread> runningThreads = Collections.newSetFromMap(new ConcurrentHashMap<Thread, Boolean>());
private final Set<RunningThread> runningThreads = Collections.newSetFromMap(new ConcurrentHashMap<RunningThread, Boolean>());

@CompilerDirectives.CompilationFinal private Assumption assumption = Truffle.getRuntime().createAssumption();
private final ReentrantLock lock = new ReentrantLock();
Expand All @@ -44,12 +44,16 @@ public SafepointManager(RubyContext context) {
}

public void enterThread() {
enterThread(true);
}

public void enterThread(boolean interruptible) {
CompilerAsserts.neverPartOfCompilation();

lock.lock();
try {
phaser.register();
runningThreads.add(Thread.currentThread());
runningThreads.add(new RunningThread(Thread.currentThread(), interruptible));
} finally {
lock.unlock();
}
Expand Down Expand Up @@ -116,15 +120,15 @@ public void pauseAllThreadsAndExecute(Consumer<RubyThread> action) {
}

public void pauseAllThreadsAndExecuteFromNonRubyThread(Consumer<RubyThread> action) {
enterThread();
enterThread(false);
try {
pauseAllThreadsAndExecute(false, action);
} finally {
leaveThread();
}
}

private void pauseAllThreadsAndExecute(boolean holdsGlobalLock, Consumer<RubyThread> action) {
public void pauseAllThreadsAndExecute(boolean holdsGlobalLock, Consumer<RubyThread> action) {
CompilerDirectives.transferToInterpreter();

if (lock.isHeldByCurrentThread()) {
Expand Down Expand Up @@ -157,9 +161,48 @@ private void pauseAllThreadsAndExecute(boolean holdsGlobalLock, Consumer<RubyThr
}

private void interruptAllThreads() {
for (Thread thread : runningThreads) {
thread.interrupt();
for (RunningThread thread : runningThreads) {
if (thread.isInterruptible()) {
thread.getThread().interrupt();
}
}
}

private class RunningThread {

private final Thread thread;
private final boolean interruptible;

This comment has been minimized.

Copy link
@chrisseaton

chrisseaton Feb 27, 2015

Author Contributor

@eregon not all threads can be interrupted without problem - for example the stack server HTTP server can't be interrupted as it closes the connection. I've added an option to register threads as not to be interrupted.

This comment has been minimized.

Copy link
@eregon

eregon Feb 28, 2015

Member

Indeed, good catch!
But this only works for the driving thread though, so maybe we could just avoid interrupting ourselves since that is useless?

This comment has been minimized.

Copy link
@chrisseaton

chrisseaton Feb 28, 2015

Author Contributor

There could be other server threads we don't want to interrupt. We currently register any thread that uses a safepoint - do we need to do that?

This comment has been minimized.

Copy link
@eregon

eregon Feb 28, 2015

Member

Non-ruby threads are registered only for the duration of the safepoint and so there is only at most one of them since pauseAll... is synchronized. Yes, we need to register with the Phaser at least.


public RunningThread(Thread thread, boolean interruptible) {
this.thread = thread;
this.interruptible = interruptible;
}

public Thread getThread() {
return thread;
}

public boolean isInterruptible() {
return interruptible;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

RunningThread that = (RunningThread) o;

if (!thread.equals(that.thread)) return false;

return true;
}

@Override
public int hashCode() {
return thread.hashCode();
}

}

}

This file was deleted.

0 comments on commit 1440adc

Please sign in to comment.