Enhanced crash report interface

- CrashReports.report now throws an exception instead of invoking System.exit()
  - Reduced chance of deadlocks
  - Improved code readability
- added CrashReports.crash to handle exceptions thrown by report() in top-level catch blocks
This commit is contained in:
Sergey Karmanov 2021-01-01 22:32:07 +03:00
parent 00773d4f8b
commit bedc9fc729
28 changed files with 87 additions and 59 deletions

View File

@ -44,7 +44,7 @@ public class ProgressiaLauncher {
CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());
Thread.setDefaultUncaughtExceptionHandler((Thread thread, Throwable t)-> {
CrashReports.report(t,"Uncaught exception in thread %s", thread.getName());
CrashReports.crash(t, "Uncaught exception in thread %s", thread.getName());
});
}

View File

@ -40,7 +40,7 @@ public class ClientProxy implements Proxy {
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
RenderTaskQueue.waitAndInvoke(() -> Typefaces.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz"))));
} catch (InterruptedException e) {
CrashReports.report(e, "ClientProxy failed");
throw CrashReports.report(e, "ClientProxy failed");
}
TestContent.registerContent();

View File

@ -35,7 +35,7 @@ public class DefaultClientCommsListener implements CommsListener {
@Override
public void onIOError(IOException reason) {
CrashReports.report(reason, "An IOException has occurred in communications");
throw CrashReports.report(reason, "An IOException has occurred in communications");
// TODO implement
}

View File

@ -41,7 +41,7 @@ public class Program implements OpenGLDeletable {
glLinkProgram(handle);
if (glGetProgrami(handle, GL_LINK_STATUS) == GL_FALSE) {
CrashReports.report(null, "Bad program:\n%s", glGetProgramInfoLog(handle));
throw CrashReports.report(null, "Bad program:\n%s", glGetProgramInfoLog(handle));
}
}

View File

@ -80,7 +80,7 @@ public class Shader implements OpenGLDeletable {
if (glGetShaderi(handle, GL_COMPILE_STATUS) == GL_FALSE) {
System.out.println("***************** ERROR ******************");
System.out.println(source);
CrashReports.report(null, "Bad shader:\n %s", glGetShaderInfoLog(handle));
throw CrashReports.report(null, "Bad shader:\n %s", glGetShaderInfoLog(handle));
}
}

View File

@ -27,7 +27,7 @@ public class Attribute {
public Attribute(int handle, Program program) {
if (handle < 0) {
CrashReports.report(null, "Bad handle: %d", handle);
throw CrashReports.report(null, "Bad handle: %d", handle);
}
this.handle = handle;

View File

@ -27,7 +27,7 @@ public class Uniform {
public Uniform(int handle, Program program) {
if (handle < 0) {
CrashReports.report(null, "Bad handle: %d", handle);
throw CrashReports.report(null, "Bad handle: %d", handle);
}
this.handle = handle;

View File

@ -57,8 +57,7 @@ public class GNUUnifontLoader {
return createStream(reader).map(GNUUnifontLoader::parse).map(GNUUnifontLoader::addToAtlas)
.collect(Collectors.collectingAndThen(createMapper(), GNUUnifont::new));
} catch (IOException | UncheckedIOException e) {
CrashReports.report(e, "Could not load GNUUnifont");
return null;
throw CrashReports.report(e, "Could not load GNUUnifont");
}
}
@ -93,8 +92,7 @@ public class GNUUnifontLoader {
return new ParsedGlyph(c, editor);
} catch (IOException e) {
CrashReports.report(e, "Could not load GNUUnifont: could not load character \"%s\"", declar);
return null;
throw CrashReports.report(e, "Could not load GNUUnifont: could not load character \"%s\"", declar);
}
}

View File

@ -235,7 +235,7 @@ public class Component extends Named {
valid = true;
} catch (Exception e) {
CrashReports.report(e, "Could not layout Component %s", this);
throw CrashReports.report(e, "Could not layout Component %s", this);
}
}
@ -248,7 +248,7 @@ public class Component extends Named {
try {
return getLayout().calculatePreferredSize(this);
} catch (Exception e) {
CrashReports.report(e, "Could not calculate preferred size for Component %s", this);
throw CrashReports.report(e, "Could not calculate preferred size for Component %s", this);
}
}
@ -517,7 +517,7 @@ public class Component extends Named {
break;
}
} catch (Exception e) {
CrashReports.report(e, "Could not dispatch input to Component %s", this);
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
}
}
@ -609,7 +609,7 @@ public class Component extends Named {
try {
assembleSelf(target);
} catch (Exception e) {
CrashReports.report(e, "Could not assemble Component %s", this);
throw CrashReports.report(e, "Could not assemble Component %s", this);
}
assembleChildren(target);
@ -617,7 +617,7 @@ public class Component extends Named {
try {
postAssembleSelf(target);
} catch (Exception e) {
CrashReports.report(e, "Post-assembly failed for Component %s", this);
throw CrashReports.report(e, "Post-assembly failed for Component %s", this);
}
}

View File

@ -77,7 +77,7 @@ public class Keys {
}
} catch (IllegalAccessException e) {
CrashReports.report(e, "Cannot access GLFW constants");
throw CrashReports.report(e, "Cannot access GLFW constants");
}
}
@ -94,7 +94,7 @@ public class Keys {
}
if (CODES_TO_NAMES.containsKey(value)) {
CrashReports.report(null, "Duplicate keys: %s and %s both map to %d(0x%s)",
throw CrashReports.report(null, "Duplicate keys: %s and %s both map to %d(0x%s)",
CODES_TO_NAMES.get(value), name, value, Integer.toHexString(value));
}

View File

@ -157,8 +157,7 @@ public class Atlases {
return loadSprite(data.getData(), group);
} catch (IOException e) {
CrashReports.report(e, "Could not load sprite %s into atlas group %s", resource, group);
return null;
throw CrashReports.report(e, "Could not load sprite %s into atlas group %s", resource, group);
}
}
@ -174,7 +173,7 @@ public class Atlases {
Atlas newAtlas = new Atlas(group);
if (!newAtlas.canAddSprite(data)) {
CrashReports.report(null, "Could not fit texture into atlas of size %d", newAtlas.getSize());
throw CrashReports.report(null, "Could not fit texture into atlas of size %d", newAtlas.getSize());
}
atlases.add(newAtlas);

View File

@ -31,8 +31,7 @@ public class SimpleTextures {
new Sprite(new TexturePrimitive(data.getData()))
);
} catch (IOException e) {
CrashReports.report(e, "Could not load texture %s", resource);
return null;
throw CrashReports.report(e, "Could not load texture %s", resource);
}
}

View File

@ -94,7 +94,7 @@ public class TexturePrimitive implements OpenGLDeletable {
OpenGLObjectTracker.register(this, GL11::glDeleteTextures);
if (handle < 0) {
CrashReports.report(null, "Failed to allocate texture");
throw CrashReports.report(null, "Failed to allocate texture");
}
}

View File

@ -43,7 +43,7 @@ public class Localizer {
data = new Parser(langFolder + this.language + ".lang").parse();
pokeListeners(language);
} else {
CrashReports.report(null, "Language not found: %s", language);
throw CrashReports.report(null, "Language not found: %s", language);
}
}

View File

@ -81,8 +81,7 @@ public class Parser {
}
} catch (IOException | EscapeException e) {
CrashReports.report(e, "Could not parse language file %s", filePath);
return null;
throw CrashReports.report(e, "Could not parse language file %s", filePath);
}
return parsedData;
}

View File

@ -29,8 +29,7 @@ public class EntityRenderRegistry extends NamespacedInstanceRegistry<EntityRende
).getData()
);
} catch (IOException e) {
CrashReports.report(e, "Could not load entity texture %s", name);
return null;
throw CrashReports.report(e, "Could not load entity texture %s", name);
}
}

View File

@ -304,7 +304,7 @@ public class Units {
try {
registerUnits(Units.class);
} catch (IllegalAccessException e) {
CrashReports.report(e, "Could not register units declared in {}", Units.class.getName());
throw CrashReports.report(e, "Could not register units declared in {}", Units.class.getName());
}
}

View File

@ -39,11 +39,10 @@ public class ChunkIO {
} catch (IOException | DecodingException e) {
throw e;
} catch (Throwable t) {
CrashReports.report(
throw CrashReports.report(
t, "Codec %s has failed to decode chunk (%d; %d; %d)",
codec.getId(), position.x, position.y, position.z
);
return null;
}
}
@ -58,7 +57,7 @@ public class ChunkIO {
} catch (IOException e) {
throw e;
} catch (Throwable t) {
CrashReports.report(
throw CrashReports.report(
t, "Codec %s has failed to encode chunk (%d; %d; %d)",
codec.getId(), chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z
);

View File

@ -51,8 +51,7 @@ public class Resource extends Named {
try (Reader reader = getReader()) {
return CharStreams.toString(reader);
} catch (IOException e) {
CrashReports.report(e, "Could not read resource %s as text", this);
return null;
throw CrashReports.report(e, "Could not read resource %s as text", this);
}
}
@ -61,8 +60,7 @@ public class Resource extends Named {
try (InputStream stream = getInputStream()) {
byteArray = ByteStreams.toByteArray(stream);
} catch (IOException e) {
CrashReports.report(e, "Could not read resource %s as bytes", this);
return null;
throw CrashReports.report(e, "Could not read resource %s as bytes", this);
}
if (output == null || output.remaining() < byteArray.length) {

View File

@ -30,17 +30,33 @@ public class CrashReports {
private static final Collection<Analyzer> ANALYZERS = Collections.synchronizedCollection(new ArrayList<>());
private static final Logger LOGGER = LogManager.getLogger("crash");
public static ReportedException report(Throwable throwable, String messageFormat, Object... args) {
if (throwable instanceof ReportedException) return (ReportedException) throwable;
return new ReportedException(throwable, messageFormat, args);
}
public static RuntimeException crash(Throwable throwable, String messageFormat, Object... args) {
if (throwable instanceof ReportedException) {
throw crash((ReportedException) throwable);
} else {
throw crash(report(throwable, messageFormat, args));
}
}
/**
* <em>This method never returns.</em>
* <p>
* TODO document
*
* @param throwable
* @param messageFormat
* @param args
* @param reportException
*/
public static void report(Throwable throwable, String messageFormat, Object... args) {
public static RuntimeException crash(ReportedException reportException) {
Throwable throwable = reportException.getCause();
String messageFormat = reportException.getMessageFormat();
Object[] args = reportException.getArgs();
StringBuilder output = new StringBuilder();
try {
@ -76,6 +92,7 @@ public class CrashReports {
export(output.toString());
System.exit(0);
return reportException;
}
private static void appendContextProviders(StringBuilder output) {
@ -233,6 +250,27 @@ public class CrashReports {
private static void addSeparator(StringBuilder sb) {
sb.append(StringUtil.sequence('-', 80)).append("\n");
}
public static class ReportedException extends RuntimeException {
private String messageFormat;
private Object[] args;
public ReportedException(Throwable throwable, String messageFormat, Object... args){
super(throwable);
this.messageFormat = messageFormat;
this.args = args;
}
public String getMessageFormat() {
return messageFormat;
}
public Object[] getArgs() {
return args;
}
}
}
class StringUtilsTemp {
@ -254,4 +292,4 @@ class StringUtilsTemp {
}
return sb.toString();
}
}
}

View File

@ -52,7 +52,7 @@ public class PacketSendChunk extends PacketChunkChange {
try {
world.addChunk(ChunkIO.load(world, position, data.getInputStream()));
} catch (DecodingException | IOException e) {
CrashReports.report(e, "Could not load chunk");
throw CrashReports.report(e, "Could not load chunk");
}
}

View File

@ -49,7 +49,7 @@ public class PacketEntityChange extends PacketWorldChange {
try {
entity.write(this.buffer.getWriter(), IOContext.COMMS);
} catch (IOException e) {
CrashReports.report(e, "Entity could not be written");
throw CrashReports.report(e, "Entity could not be written");
}
}
@ -71,13 +71,13 @@ public class PacketEntityChange extends PacketWorldChange {
EntityData entity = world.getEntity(getEntityId());
if (entity == null) {
CrashReports.report(null, "Entity with ID %d not found", getEntityId());
throw CrashReports.report(null, "Entity with ID %d not found", getEntityId());
}
try {
entity.read(getReader(), IOContext.COMMS);
} catch (IOException e) {
CrashReports.report(e, "Entity could not be read");
throw CrashReports.report(e, "Entity could not be read");
}
}

View File

@ -34,7 +34,7 @@ public class PacketSendEntity extends PacketWorldChange {
try {
entity.write(this.buffer.getWriter(), IOContext.COMMS);
} catch (IOException e) {
CrashReports.report(e, "Could not write an entity into an internal buffer");
throw CrashReports.report(e, "Could not write an entity into an internal buffer");
}
}
@ -61,7 +61,7 @@ public class PacketSendEntity extends PacketWorldChange {
try {
entity.read(this.buffer.getReader(), IOContext.COMMS);
} catch (IOException e) {
CrashReports.report(e, "Could not read an entity from an internal buffer");
throw CrashReports.report(e, "Could not read an entity from an internal buffer");
}
world.addEntity(entity);

View File

@ -44,8 +44,7 @@ public class PlayerManager {
EntityData entity = spawnPlayerEntity(login);
return entity;
} else {
CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
return null;
throw CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
}
}

View File

@ -57,7 +57,7 @@ public class ServerThread implements Runnable {
server.tick();
ticker.runOneTick();
} catch (Exception e) {
CrashReports.report(e, "Got an exception in the server thread");
throw CrashReports.crash(e, "Got an exception in the server thread");
}
}

View File

@ -22,7 +22,7 @@ public class TickAndUpdateUtil {
try {
block.tick(context);
} catch (Exception e) {
CrashReports.report(e, "Could not tick block {}", block);
throw CrashReports.report(e, "Could not tick block {}", block);
}
}
@ -38,7 +38,7 @@ public class TickAndUpdateUtil {
try {
tile.tick(context);
} catch (Exception e) {
CrashReports.report(e, "Could not tick tile {}", tile);
throw CrashReports.report(e, "Could not tick tile {}", tile);
}
}
@ -63,7 +63,7 @@ public class TickAndUpdateUtil {
try {
block.update(context);
} catch (Exception e) {
CrashReports.report(e, "Could not update block {}", block);
throw CrashReports.report(e, "Could not update block {}", block);
}
}
@ -79,7 +79,7 @@ public class TickAndUpdateUtil {
try {
tile.update(context);
} catch (Exception e) {
CrashReports.report(e, "Could not update tile {}", tile);
throw CrashReports.report(e, "Could not update tile {}", tile);
}
}
@ -104,7 +104,7 @@ public class TickAndUpdateUtil {
try {
logic.tick(data, context);
} catch (Exception e) {
CrashReports.report(e, "Could not tick entity {}", logic);
throw CrashReports.report(e, "Could not tick entity {}", logic);
}
}

View File

@ -147,7 +147,7 @@ class Ticker {
try {
task.run(srv);
} catch (Exception e) {
CrashReports.report(e, "Could not run %s task %s", task.getClass().getSimpleName(), task);
throw CrashReports.report(e, "Could not run %s task %s", task.getClass().getSimpleName(), task);
}
tasksCompleted++;

View File

@ -260,8 +260,8 @@ public class TickerCoordinator {
if (t instanceof ConcurrentModificationException) {
logger.debug("javahorse kill urself");
}
CrashReports.report(
throw CrashReports.crash(
t,
"Something has gone horribly wrong in server ticker code "
+ "(thread %s) and it is (probably) not related to mods or devils.",