From 577af720d309794129b7e15a22565af3ad889ec0 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Tue, 2 Aug 2016 12:12:49 -1000 Subject: [PATCH] Add proper file-based error reporting --- hecl/blender/BlenderConnection.cpp | 42 +++++- hecl/blender/BlenderConnection.hpp | 2 + hecl/blender/hecl_blendershell.py | 221 ++++++++++++++++------------- 3 files changed, 156 insertions(+), 109 deletions(-) diff --git a/hecl/blender/BlenderConnection.cpp b/hecl/blender/BlenderConnection.cpp index 93f74ebce..53da76747 100644 --- a/hecl/blender/BlenderConnection.cpp +++ b/hecl/blender/BlenderConnection.cpp @@ -3,9 +3,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include @@ -103,7 +106,7 @@ size_t BlenderConnection::_readLine(char* buf, size_t bufSz) *buf = '\0'; if (readBytes >= 4) if (!memcmp(buf, "EXCEPTION", std::min(readBytes, size_t(9)))) - BlenderLog.report(logvisor::Fatal, "Blender exception"); + _blenderDied(); return readBytes; } ++readBytes; @@ -114,7 +117,7 @@ size_t BlenderConnection::_readLine(char* buf, size_t bufSz) *buf = '\0'; if (readBytes >= 4) if (!memcmp(buf, "EXCEPTION", std::min(readBytes, size_t(9)))) - BlenderLog.report(logvisor::Fatal, "Blender exception"); + _blenderDied(); return readBytes; } } @@ -131,7 +134,7 @@ size_t BlenderConnection::_writeLine(const char* buf) goto err; return (size_t)ret; err: - BlenderLog.report(logvisor::Fatal, strerror(errno)); + _blenderDied(); return 0; } @@ -142,10 +145,10 @@ size_t BlenderConnection::_readBuf(void* buf, size_t len) goto err; if (len >= 4) if (!memcmp((char*)buf, "EXCEPTION", std::min(len, size_t(9)))) - BlenderLog.report(logvisor::Fatal, "Blender exception"); + _blenderDied(); return ret; err: - BlenderLog.report(logvisor::Fatal, strerror(errno)); + _blenderDied(); return 0; } @@ -156,7 +159,7 @@ size_t BlenderConnection::_writeBuf(const void* buf, size_t len) goto err; return ret; err: - BlenderLog.report(logvisor::Fatal, strerror(errno)); + _blenderDied(); return 0; } @@ -166,9 +169,30 @@ void BlenderConnection::_closePipe() close(m_writepipe[1]); } +void BlenderConnection::_blenderDied() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + FILE* errFp = hecl::Fopen(m_errPath.c_str(), _S("r")); + if (errFp) + { + fseek(errFp, 0, SEEK_END); + int64_t len = hecl::FTell(errFp); + if (len) + { + fseek(errFp, 0, SEEK_SET); + std::unique_ptr buf(new char[len+1]); + memset(buf.get(), 0, len+1); + fread(buf.get(), 1, len, errFp); + BlenderLog.report(logvisor::Fatal, "\n%s", buf.get()); + } + } + BlenderLog.report(logvisor::Fatal, "Blender Exception"); +} + BlenderConnection::BlenderConnection(int verbosityLevel) { BlenderLog.report(logvisor::Info, "Establishing BlenderConnection..."); + signal(SIGPIPE, SIG_IGN); /* Put hecl_blendershell.py in temp dir */ #ifdef _WIN32 @@ -320,6 +344,10 @@ BlenderConnection::BlenderConnection(int verbosityLevel) m_blenderProc = pid; #endif + /* Stash error path an unlink existing file */ + m_errPath = hecl::SystemString(TMPDIR) + hecl::Format(_S("/hecl_%016llX.derp"), (unsigned long long)m_blenderProc); + hecl::Unlink(m_errPath.c_str()); + /* Handle first response */ char lineBuf[256]; _readLine(lineBuf, sizeof(lineBuf)); @@ -398,7 +426,7 @@ BlenderConnection::PyOutStream::StreamBuf::overflow(int_type ch) { if (m_deleteOnError) m_parent.m_parent->deleteBlend(); - BlenderLog.report(logvisor::Fatal, "error sending '%s' to blender", m_lineBuf.c_str()); + m_parent.m_parent->_blenderDied(); } m_lineBuf.clear(); return ch; diff --git a/hecl/blender/BlenderConnection.hpp b/hecl/blender/BlenderConnection.hpp index c958c1de7..efcb33278 100644 --- a/hecl/blender/BlenderConnection.hpp +++ b/hecl/blender/BlenderConnection.hpp @@ -61,11 +61,13 @@ private: bool m_loadedRigged = false; ProjectPath m_loadedBlend; std::string m_startupBlend; + hecl::SystemString m_errPath; size_t _readLine(char* buf, size_t bufSz); size_t _writeLine(const char* buf); size_t _readBuf(void* buf, size_t len); size_t _writeBuf(const void* buf, size_t len); void _closePipe(); + void _blenderDied(); public: BlenderConnection(int verbosityLevel=1); ~BlenderConnection(); diff --git a/hecl/blender/hecl_blendershell.py b/hecl/blender/hecl_blendershell.py index f6e15de30..3340a0090 100644 --- a/hecl/blender/hecl_blendershell.py +++ b/hecl/blender/hecl_blendershell.py @@ -10,10 +10,19 @@ args = sys.argv[sys.argv.index('--')+1:] readfd = int(args[0]) writefd = int(args[1]) verbosity_level = int(args[2]) +err_path = "" if sys.platform == "win32": import msvcrt readfd = msvcrt.open_osfhandle(readfd, os.O_RDONLY | os.O_BINARY) writefd = msvcrt.open_osfhandle(writefd, os.O_WRONLY | os.O_BINARY) + err_path = "/Temp" +else: + err_path = "/tmp" + +if 'TMPDIR' in os.environ: + err_path = os.environ['TMPDIR'] + +err_path += "/hecl_%016X.derp" % os.getpid() def readpipeline(): retval = bytearray() @@ -230,125 +239,133 @@ def dataout_loop(): writepipebuf(struct.pack('f', c)) -# Command loop -while True: - cmdargs = read_cmdargs() - print(cmdargs) +# Main exception handling +try: + # Command loop + while True: + cmdargs = read_cmdargs() + print(cmdargs) - if cmdargs[0] == 'QUIT': - quitblender() + if cmdargs[0] == 'QUIT': + quitblender() - elif cmdargs[0] == 'OPEN': - if 'FINISHED' in bpy.ops.wm.open_mainfile(filepath=cmdargs[1]): - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode = 'OBJECT') - writepipeline(b'FINISHED') - else: - writepipeline(b'CANCELLED') - - elif cmdargs[0] == 'CREATE': - if len(cmdargs) >= 4: - bpy.ops.wm.open_mainfile(filepath=cmdargs[3]) - else: - bpy.ops.wm.read_homefile() - bpy.context.user_preferences.filepaths.save_version = 0 - if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=cmdargs[1]): - bpy.ops.file.hecl_patching_load() - bpy.context.scene.hecl_type = cmdargs[2] - writepipeline(b'FINISHED') - else: - writepipeline(b'CANCELLED') - - elif cmdargs[0] == 'GETTYPE': - writepipeline(bpy.context.scene.hecl_type.encode()) - - elif cmdargs[0] == 'GETMESHRIGGED': - meshName = bpy.context.scene.hecl_mesh_obj - if meshName not in bpy.data.objects: - writepipeline(b'FALSE') - else: - if len(bpy.data.objects[meshName].vertex_groups): - writepipeline(b'TRUE') + elif cmdargs[0] == 'OPEN': + if 'FINISHED' in bpy.ops.wm.open_mainfile(filepath=cmdargs[1]): + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode = 'OBJECT') + writepipeline(b'FINISHED') else: + writepipeline(b'CANCELLED') + + elif cmdargs[0] == 'CREATE': + if len(cmdargs) >= 4: + bpy.ops.wm.open_mainfile(filepath=cmdargs[3]) + else: + bpy.ops.wm.read_homefile() + bpy.context.user_preferences.filepaths.save_version = 0 + if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=cmdargs[1]): + bpy.ops.file.hecl_patching_load() + bpy.context.scene.hecl_type = cmdargs[2] + writepipeline(b'FINISHED') + else: + writepipeline(b'CANCELLED') + + elif cmdargs[0] == 'GETTYPE': + writepipeline(bpy.context.scene.hecl_type.encode()) + + elif cmdargs[0] == 'GETMESHRIGGED': + meshName = bpy.context.scene.hecl_mesh_obj + if meshName not in bpy.data.objects: writepipeline(b'FALSE') + else: + if len(bpy.data.objects[meshName].vertex_groups): + writepipeline(b'TRUE') + else: + writepipeline(b'FALSE') - elif cmdargs[0] == 'SAVE': - bpy.context.user_preferences.filepaths.save_version = 0 - if 'FINISHED' in bpy.ops.wm.save_mainfile(check_existing=False, compress=True): - writepipeline(b'FINISHED') - else: - writepipeline(b'CANCELLED') + elif cmdargs[0] == 'SAVE': + bpy.context.user_preferences.filepaths.save_version = 0 + if 'FINISHED' in bpy.ops.wm.save_mainfile(check_existing=False, compress=True): + writepipeline(b'FINISHED') + else: + writepipeline(b'CANCELLED') - elif cmdargs[0] == 'PYBEGIN': - writepipeline(b'READY') - globals = {'hecl':hecl} - compbuf = str() - bracket_count = 0 - while True: - try: - line = readpipeline() + elif cmdargs[0] == 'PYBEGIN': + writepipeline(b'READY') + globals = {'hecl':hecl} + compbuf = str() + bracket_count = 0 + while True: + try: + line = readpipeline() - # ANIM check - if line == b'PYANIM': - # Ensure remaining block gets executed + # ANIM check + if line == b'PYANIM': + # Ensure remaining block gets executed + if len(compbuf): + exec_compbuf(compbuf, globals) + compbuf = str() + animin_loop(globals) + continue + + # End check + elif line == b'PYEND': + # Ensure remaining block gets executed + if len(compbuf): + exec_compbuf(compbuf, globals) + compbuf = str() + writepipeline(b'DONE') + break + + # Syntax filter + linestr = line.decode().rstrip() + if not len(linestr) or linestr.lstrip()[0] == '#': + writepipeline(b'OK') + continue + leading_spaces = len(linestr) - len(linestr.lstrip()) + + # Block lines always get appended right away + if linestr.endswith(':') or leading_spaces or bracket_count: + if len(compbuf): + compbuf += '\n' + compbuf += linestr + bracket_count += count_brackets(linestr) + writepipeline(b'OK') + continue + + # Complete non-block statement in compbuf if len(compbuf): exec_compbuf(compbuf, globals) - compbuf = str() - animin_loop(globals) - continue - # End check - elif line == b'PYEND': - # Ensure remaining block gets executed - if len(compbuf): - exec_compbuf(compbuf, globals) - compbuf = str() - writepipeline(b'DONE') - break - - # Syntax filter - linestr = line.decode().rstrip() - if not len(linestr) or linestr.lstrip()[0] == '#': - writepipeline(b'OK') - continue - leading_spaces = len(linestr) - len(linestr.lstrip()) - - # Block lines always get appended right away - if linestr.endswith(':') or leading_spaces or bracket_count: - if len(compbuf): - compbuf += '\n' - compbuf += linestr + # Establish new compbuf + compbuf = linestr bracket_count += count_brackets(linestr) - writepipeline(b'OK') - continue - # Complete non-block statement in compbuf - if len(compbuf): - exec_compbuf(compbuf, globals) + except Exception as e: + writepipeline(b'EXCEPTION') + raise + break + writepipeline(b'OK') - # Establish new compbuf - compbuf = linestr - bracket_count += count_brackets(linestr) + elif cmdargs[0] == 'PYEND': + writepipeline(b'ERROR') + elif cmdargs[0] == 'DATABEGIN': + try: + dataout_loop() except Exception as e: writepipeline(b'EXCEPTION') raise - break - writepipeline(b'OK') - elif cmdargs[0] == 'PYEND': - writepipeline(b'ERROR') + elif cmdargs[0] == 'DATAEND': + writepipeline(b'ERROR') - elif cmdargs[0] == 'DATABEGIN': - try: - dataout_loop() - except Exception as e: - writepipeline(b'EXCEPTION') - raise - - elif cmdargs[0] == 'DATAEND': - writepipeline(b'ERROR') - - else: - hecl.command(cmdargs, writepipeline, writepipebuf) + else: + hecl.command(cmdargs, writepipeline, writepipebuf) +except Exception: + import traceback + fout = open(err_path, 'w') + traceback.print_exc(file=fout) + fout.close() + raise