diff --git a/hpcgap/lib/hpc/stdtasks.g b/hpcgap/lib/hpc/stdtasks.g index 59f0991c14..55f7a60db8 100644 --- a/hpcgap/lib/hpc/stdtasks.g +++ b/hpcgap/lib/hpc/stdtasks.g @@ -18,7 +18,7 @@ TASKS := AtomicRecord( rec ( WorkerThread := function(context) local task; BreakOnError := false; - SilentErrors := true; + SilentNonInteractiveErrors := true; WaitSemaphore(context.semaphore); while true do WaitSemaphore(context.semaphore); diff --git a/lib/error.g b/lib/error.g index 4d5e26ca7f..74bc769450 100644 --- a/lib/error.g +++ b/lib/error.g @@ -114,9 +114,10 @@ BIND_GLOBAL("ErrorInner", function( arg ) local context, mayReturnVoid, mayReturnObj, lateMessage, earlyMessage, x, prompt, res, errorLVars, justQuit, printThisStatement, - location, lastErrorStream, shellOut, shellIn; + printEarlyMessage, printEarlyTraceback, lastErrorStream, + shellOut, shellIn; - context := arg[1].context; + context := arg[1].context; if not IsLVarsBag(context) then PrintTo("*errout*", "ErrorInner: option context must be a local variables bag\n"); LEAVE_ALL_NAMESPACES(); @@ -184,26 +185,77 @@ BIND_GLOBAL("ErrorInner", LEAVE_ALL_NAMESPACES(); JUMP_TO_CATCH(1); fi; - + + # Local functions that print the user feedback. + printEarlyMessage := function(stream) + PrintTo(stream, "Error, "); + # earlyMessage usually contains information about what went wrong. + for x in earlyMessage do + PrintTo(stream, x); + od; + end; + + printEarlyTraceback := function(stream) + local location; + if printThisStatement then + if context <> GetBottomLVars() then + PrintTo(stream, " in\n "); + PRINT_CURRENT_STATEMENT(stream, context); + PrintTo(stream, " called from "); + fi; + else + location := CURRENT_STATEMENT_LOCATION(context); + if location <> fail then + PrintTo(stream, " at ", location[1], ":", location[2]); + fi; + PrintTo(stream, " called from"); + fi; + PrintTo("*errout*", "\n"); + end; + ErrorLevel := ErrorLevel+1; ERROR_COUNT := ERROR_COUNT+1; errorLVars := ErrorLVars; ErrorLVars := context; - # BreakOnError is defined by the `-T` command line flag in init.g + # Do we want to skip the break loop? + # BreakOnError is initialized by the `-T` command line flag in init.g if QUITTING or not BreakOnError then - if not SilentErrors then - PrintTo("*errout*", "Error, "); - for x in earlyMessage do - PrintTo("*errout*", x); - od; - PrintTo("*errout*", "\n"); + # If we skip the break loop, the standard behaviour is to print only + # the earlyMessage. If SilentNonInteractiveErrors is true we do not + # print any messages. If AlwaysPrintTracebackOnError is true we also + # call OnBreak(), which by default prints the traceback. + # SilentNonInteractiveErrors superseeds AlwaysPrintTracebackOnError. + # It is used by HPC-GAP to e.g. suppress error messages in worker + # threads. + if not SilentNonInteractiveErrors then + printEarlyMessage("*errout*"); + if AlwaysPrintTracebackOnError then + printEarlyTraceback("*errout*"); + if IsBound(OnBreak) and IsFunction(OnBreak) then + OnBreak(); + fi; + else + PrintTo("*errout*", "\n"); + fi; fi; if IsHPCGAP then + # In HPC-GAP we want to access error messages encountered in + # tasks via TaskError. To this end we store the error message + # in the thread local variable LastErrorMessage. LastErrorMessage := ""; lastErrorStream := OutputTextString(LastErrorMessage, true); - for x in earlyMessage do - PrintTo(lastErrorStream, x); - od; + printEarlyMessage(lastErrorStream); + if AlwaysPrintTracebackOnError then + printEarlyTraceback(lastErrorStream); + # FIXME: Also make HPCGAP work with OnBreak(). + # If AlwaysPrintTracebackOnError is true, the output of + # OnBreak() should also be put into LastErrorMessage. + # To do this there needs to be a way to put its output + # into lastErrorStream. + # OnBreak() is documented to not take any arguments. + # One could work around that if there were e.g. a GAP error + # stream which all error functions print to. + fi; CloseStream(lastErrorStream); MakeImmutable(LastErrorMessage); fi; @@ -212,33 +264,26 @@ BIND_GLOBAL("ErrorInner", if ErrorLevel = 0 then LEAVE_ALL_NAMESPACES(); fi; JUMP_TO_CATCH(0); fi; - PrintTo("*errout*", "Error, "); - for x in earlyMessage do - PrintTo("*errout*", x); - od; - if printThisStatement then - if context <> GetBottomLVars() then - PrintTo("*errout*", " in\n "); - PRINT_CURRENT_STATEMENT("*errout*", context); - PrintTo("*errout*", " called from \n"); - else - PrintTo("*errout*", "\n"); - fi; - else - location := CURRENT_STATEMENT_LOCATION(context); - if location <> fail then - PrintTo("*errout*", " at ", location[1], ":", location[2]); - fi; - PrintTo("*errout*", " called from\n"); - fi; + + printEarlyMessage("*errout*"); + printEarlyTraceback("*errout*"); if SHOULD_QUIT_ON_BREAK() then + # Again, the default is to not print the rest of the traceback. + # If AlwaysPrintTracebackOnError is true we do so anyways. + if AlwaysPrintTracebackOnError + and IsBound(OnBreak) and IsFunction(OnBreak) then + OnBreak(); + fi; FORCE_QUIT_GAP(1); fi; + # OnBreak() is set to Where() by default, which prints the traceback. if IsBound(OnBreak) and IsFunction(OnBreak) then OnBreak(); fi; + + # Now print lateMessage and OnBreakMessage a la "press return; to .." if IsString(lateMessage) then PrintTo("*errout*", lateMessage,"\n"); elif lateMessage then @@ -270,10 +315,10 @@ BIND_GLOBAL("ErrorInner", if IsBound(OnQuit) and IsFunction(OnQuit) then OnQuit(); fi; - if ErrorLevel = 0 then LEAVE_ALL_NAMESPACES(); fi; + if ErrorLevel = 0 then LEAVE_ALL_NAMESPACES(); fi; if not justQuit then - # dont try and do anything else after this before the longjump - SetUserHasQuit(1); + # dont try and do anything else after this before the longjump + SetUserHasQuit(1); fi; JUMP_TO_CATCH(3); fi; diff --git a/lib/init.g b/lib/init.g index 573f8ac39a..bb5f570b20 100644 --- a/lib/init.g +++ b/lib/init.g @@ -236,6 +236,8 @@ fi; ## ## - Unbind `DEBUG_LOADING', since later the `-D' option can be checked. ## - Set or disable break loop according to the `-T' option. +## - Set whether traceback may be suppressed (e.g. by `-T') according to the +## `--alwaystrace' option. ## CallAndInstallPostRestore( function() if DEBUG_LOADING then @@ -247,12 +249,22 @@ CallAndInstallPostRestore( function() UNBIND_GLOBAL( "DEBUG_LOADING" ); if IsHPCGAP then + # In HPC-GAP we want to decide how to handle errors on a per thread + # basis. E.g. the break loop can be disabled in a worker thread that + # runs tasks. BindThreadLocal( "BreakOnError", not GAPInfo.CommandLineOptions.T ); - BindThreadLocal( "SilentErrors", false ); + BindThreadLocal( + "AlwaysPrintTracebackOnError", + GAPInfo.CommandLineOptions.alwaystrace + ); + BindThreadLocal( "SilentNonInteractiveErrors", false ); + # We store the error messages on a per thread basis to be able to + # e.g. access them independently from the main thread. BindThreadLocal( "LastErrorMessage", "" ); else ASS_GVAR( "BreakOnError", not GAPInfo.CommandLineOptions.T ); - ASS_GVAR( "SilentErrors", false ); + ASS_GVAR( "AlwaysPrintTracebackOnError", GAPInfo.CommandLineOptions.alwaystrace ); + ASS_GVAR( "SilentNonInteractiveErrors", false ); fi; end); diff --git a/lib/system.g b/lib/system.g index 3001102f7e..311e9b45d0 100644 --- a/lib/system.g +++ b/lib/system.g @@ -94,7 +94,8 @@ BIND_GLOBAL( "GAPInfo", rec( rec( short:= "M", default := false, help := ["disable/enable loading of compiled modules"] ), rec( short:= "N", default := false, help := ["do not use hidden implications"] ), rec( short:= "O", default := false, help := ["disable/enable loading of obsolete files"] ), - rec( short:= "T", default := false, help := ["disable/enable break loop and error traceback"] ), + rec( short:= "T", long := "nobreakloop", default := false, help := ["disable/enable break loop and error traceback"] ), + rec( long := "alwaystrace", default := false, help := ["always print error traceback (overrides behaviour of -T)"] ), rec( long := "quitonbreak", default := false, help := ["quit GAP with non-zero return value instead of entering break loop"]), , rec( short:= "L", default := "", arg := "", help := [ "restore a saved workspace"] ),