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()); CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());
Thread.setDefaultUncaughtExceptionHandler((Thread thread, Throwable t)-> { 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(WorldRenderProgram::init);
RenderTaskQueue.waitAndInvoke(() -> Typefaces.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz")))); RenderTaskQueue.waitAndInvoke(() -> Typefaces.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz"))));
} catch (InterruptedException e) { } catch (InterruptedException e) {
CrashReports.report(e, "ClientProxy failed"); throw CrashReports.report(e, "ClientProxy failed");
} }
TestContent.registerContent(); TestContent.registerContent();

View File

@ -35,7 +35,7 @@ public class DefaultClientCommsListener implements CommsListener {
@Override @Override
public void onIOError(IOException reason) { 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 // TODO implement
} }

View File

@ -41,7 +41,7 @@ public class Program implements OpenGLDeletable {
glLinkProgram(handle); glLinkProgram(handle);
if (glGetProgrami(handle, GL_LINK_STATUS) == GL_FALSE) { 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) { if (glGetShaderi(handle, GL_COMPILE_STATUS) == GL_FALSE) {
System.out.println("***************** ERROR ******************"); System.out.println("***************** ERROR ******************");
System.out.println(source); 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) { public Attribute(int handle, Program program) {
if (handle < 0) { if (handle < 0) {
CrashReports.report(null, "Bad handle: %d", handle); throw CrashReports.report(null, "Bad handle: %d", handle);
} }
this.handle = handle; this.handle = handle;

View File

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

View File

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

View File

@ -235,7 +235,7 @@ public class Component extends Named {
valid = true; valid = true;
} catch (Exception e) { } 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 { try {
return getLayout().calculatePreferredSize(this); return getLayout().calculatePreferredSize(this);
} catch (Exception e) { } 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; break;
} }
} catch (Exception e) { } 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 { try {
assembleSelf(target); assembleSelf(target);
} catch (Exception e) { } catch (Exception e) {
CrashReports.report(e, "Could not assemble Component %s", this); throw CrashReports.report(e, "Could not assemble Component %s", this);
} }
assembleChildren(target); assembleChildren(target);
@ -617,7 +617,7 @@ public class Component extends Named {
try { try {
postAssembleSelf(target); postAssembleSelf(target);
} catch (Exception e) { } 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) { } 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)) { 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)); CODES_TO_NAMES.get(value), name, value, Integer.toHexString(value));
} }

View File

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

View File

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

View File

@ -94,7 +94,7 @@ public class TexturePrimitive implements OpenGLDeletable {
OpenGLObjectTracker.register(this, GL11::glDeleteTextures); OpenGLObjectTracker.register(this, GL11::glDeleteTextures);
if (handle < 0) { 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(); data = new Parser(langFolder + this.language + ".lang").parse();
pokeListeners(language); pokeListeners(language);
} else { } 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) { } catch (IOException | EscapeException e) {
CrashReports.report(e, "Could not parse language file %s", filePath); throw CrashReports.report(e, "Could not parse language file %s", filePath);
return null;
} }
return parsedData; return parsedData;
} }

View File

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

View File

@ -304,7 +304,7 @@ public class Units {
try { try {
registerUnits(Units.class); registerUnits(Units.class);
} catch (IllegalAccessException e) { } 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) { } catch (IOException | DecodingException e) {
throw e; throw e;
} catch (Throwable t) { } catch (Throwable t) {
CrashReports.report( throw CrashReports.report(
t, "Codec %s has failed to decode chunk (%d; %d; %d)", t, "Codec %s has failed to decode chunk (%d; %d; %d)",
codec.getId(), position.x, position.y, position.z codec.getId(), position.x, position.y, position.z
); );
return null;
} }
} }
@ -58,7 +57,7 @@ public class ChunkIO {
} catch (IOException e) { } catch (IOException e) {
throw e; throw e;
} catch (Throwable t) { } catch (Throwable t) {
CrashReports.report( throw CrashReports.report(
t, "Codec %s has failed to encode chunk (%d; %d; %d)", t, "Codec %s has failed to encode chunk (%d; %d; %d)",
codec.getId(), chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z 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()) { try (Reader reader = getReader()) {
return CharStreams.toString(reader); return CharStreams.toString(reader);
} catch (IOException e) { } catch (IOException e) {
CrashReports.report(e, "Could not read resource %s as text", this); throw CrashReports.report(e, "Could not read resource %s as text", this);
return null;
} }
} }
@ -61,8 +60,7 @@ public class Resource extends Named {
try (InputStream stream = getInputStream()) { try (InputStream stream = getInputStream()) {
byteArray = ByteStreams.toByteArray(stream); byteArray = ByteStreams.toByteArray(stream);
} catch (IOException e) { } catch (IOException e) {
CrashReports.report(e, "Could not read resource %s as bytes", this); throw CrashReports.report(e, "Could not read resource %s as bytes", this);
return null;
} }
if (output == null || output.remaining() < byteArray.length) { 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 Collection<Analyzer> ANALYZERS = Collections.synchronizedCollection(new ArrayList<>());
private static final Logger LOGGER = LogManager.getLogger("crash"); 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> * <em>This method never returns.</em>
* <p> * <p>
* TODO document * TODO document
* *
* @param throwable * @param reportException
* @param messageFormat
* @param args
*/ */
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(); StringBuilder output = new StringBuilder();
try { try {
@ -76,6 +92,7 @@ public class CrashReports {
export(output.toString()); export(output.toString());
System.exit(0); System.exit(0);
return reportException;
} }
private static void appendContextProviders(StringBuilder output) { private static void appendContextProviders(StringBuilder output) {
@ -233,6 +250,27 @@ public class CrashReports {
private static void addSeparator(StringBuilder sb) { private static void addSeparator(StringBuilder sb) {
sb.append(StringUtil.sequence('-', 80)).append("\n"); 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 { class StringUtilsTemp {
@ -254,4 +292,4 @@ class StringUtilsTemp {
} }
return sb.toString(); return sb.toString();
} }
} }

View File

@ -52,7 +52,7 @@ public class PacketSendChunk extends PacketChunkChange {
try { try {
world.addChunk(ChunkIO.load(world, position, data.getInputStream())); world.addChunk(ChunkIO.load(world, position, data.getInputStream()));
} catch (DecodingException | IOException e) { } 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 { try {
entity.write(this.buffer.getWriter(), IOContext.COMMS); entity.write(this.buffer.getWriter(), IOContext.COMMS);
} catch (IOException e) { } 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()); EntityData entity = world.getEntity(getEntityId());
if (entity == null) { 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 { try {
entity.read(getReader(), IOContext.COMMS); entity.read(getReader(), IOContext.COMMS);
} catch (IOException e) { } 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 { try {
entity.write(this.buffer.getWriter(), IOContext.COMMS); entity.write(this.buffer.getWriter(), IOContext.COMMS);
} catch (IOException e) { } 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 { try {
entity.read(this.buffer.getReader(), IOContext.COMMS); entity.read(this.buffer.getReader(), IOContext.COMMS);
} catch (IOException e) { } 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); world.addEntity(entity);

View File

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

View File

@ -57,7 +57,7 @@ public class ServerThread implements Runnable {
server.tick(); server.tick();
ticker.runOneTick(); ticker.runOneTick();
} catch (Exception e) { } 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 { try {
block.tick(context); block.tick(context);
} catch (Exception e) { } 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 { try {
tile.tick(context); tile.tick(context);
} catch (Exception e) { } 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 { try {
block.update(context); block.update(context);
} catch (Exception e) { } 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 { try {
tile.update(context); tile.update(context);
} catch (Exception e) { } 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 { try {
logic.tick(data, context); logic.tick(data, context);
} catch (Exception e) { } 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 { try {
task.run(srv); task.run(srv);
} catch (Exception e) { } 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++; tasksCompleted++;

View File

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