[Closed] Inform about max crash
Sometimes a working script can causes the NOT RESPONDING of 3dsMax – the script works, in the Task Manager the 3dsmax.exe is marked as Not responding, but when the script finish its job the 3dsmax.exe is no longer Nor responding and you can use it.
Yes, it’s true! In my case, the script will be not heavy, that’s why I think if max is more then 30 seconds or then 1 minute not responding, this means that I would better force restart it.
Here is another approach. This one is a little Shell Application that runs Max and watches for crashes and “hangs”.
Same as my previews example, you need to fill-in the email and server information in order for the app to send emails.
Once you have setup that information, the script will create a file "C:\MaxGuard.exe".
This command-line application needs 3 parameters:
- The path to the 3ds Max .EXE file
- The initial time interval to start watching for inactivity (in seconds)
- Timeout to wait when Max hangs (seconds)
After those parameters, you should be able to pass any additional Max command-line parameter, but I havent tested it too much really.
So, for example, you could have a .BAT file with something like:
MaxGuard.exe "C:\3ds Max 2014\3dsmax.exe" 180 60 "C: est_scene.max"
Start 3ds Max, open “test_scene.max” file and wait 3 minutes until start running the timer with an interval of 1 minute.
(
fn CreateConsoleAssembly mSource mFilename =
(
cSharp = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
cParameters = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
cParameters.ReferencedAssemblies.Add "System.dll"
cParameters.ReferencedAssemblies.Add "System.Windows.Forms.dll"
cParameters.GenerateInMemory = true
cParameters.CompilerOptions = "/target:winexe"
cParameters.OutputAssembly = mFilename
cResult = cSharp.CompileAssemblyFromSource cParameters #(mSource)
)
-- ================================================================================================
-- CUSTOMIZE YOUR INFOMATION HERE
-- ================================================================================================
SEND_EMAIL_NOTIFICATION = true
DISPLAY_NOTIFICATION = false
FROM_ADRESS = "mike@moldova.com"
TO_ADRESS = "mike@moldova.com"
USER_NAME = "mike@moldova.com"
USER_PASS = "xxxxxxxxxxxxxxxx"
SMTP_HOTS = "smpt.moldova.com"
SMTP_PORT = 25
-- ================================================================================================
scr = "using System;
"
scr += "using System.Timers;
"
scr += "using System.Diagnostics;
"
scr += "using System.Windows.Forms;
"
scr += "public class MaxCrashReport
"
scr += "{
"
scr += " private static System.Timers.Timer pTimer;
"
scr += " private static Process pProc;
"
scr += " private static int pLastTime = 0;
"
scr += " private static int pTimeout = 1000;
"
scr += " private static Stopwatch stopWatch;
"
scr += " private static bool pTimeoutExit = false;
"
scr += " private static bool pSendEmailNotification = " + (SEND_EMAIL_NOTIFICATION as string) + ";
"
scr += " private static bool pDisplayErrorMessage = " + (DISPLAY_NOTIFICATION as string) + ";
"
scr += " public static void Main (string[] mArgs)
"
scr += " {
"
scr += " // Minimum implementation
"
scr += " if (mArgs.Length < 3)
"
scr += " {
"
scr += " MessageBox.Show(\"Invalid 3DS Max Path, Interval or StartInterval values.\");
"
scr += " System.Environment.Exit(0);
"
scr += " }
"
scr += " ProcessStartInfo startInfo = new ProcessStartInfo();
"
scr += " string arguments = \"\";
"
scr += " for (int i = 3; i < mArgs.Length; i++) arguments += mArgs[i].ToString() + \" \";
"
scr += " startInfo.Arguments = arguments;
"
scr += " startInfo.FileName = mArgs[0];
"
scr += " pProc = Process.Start(startInfo);
"
scr += " int pInitialInterval = System.Int32.Parse(mArgs[1])*1000;
"
scr += " pTimeout = System.Int32.Parse(mArgs[2])*1000;
"
scr += " if (pTimeout > 0) StartTimer(pInitialInterval);
"
scr += " stopWatch = new Stopwatch();
"
scr += " pProc.WaitForExit();
"
scr += " int exitCode = pProc.ExitCode;
"
scr += " if (pTimeoutExit) exitCode = -2;
"
scr += " string msg = \"3ds Max Ended - Reason: \";
"
scr += " switch (exitCode)
"
scr += " {
"
scr += " case -2:
"
scr += " msg += \"Timeout\";
"
scr += " break;
"
scr += " case -1:
"
scr += " msg += \"crash\";
"
scr += " break;
"
scr += " case 0:
"
scr += " msg += \"Normal\";
"
scr += " break;
"
scr += " case 1:
"
scr += " msg += \"Unexpected Process End\";
"
scr += " break;
"
scr += " default:
"
scr += " msg += \"Unknown\";
"
scr += " break;
"
scr += " }
"
scr += " if (pSendEmailNotification) SendEmail (msg, msg);
"
scr += " if (pDisplayErrorMessage) MessageBox.Show(msg + \" [\" + exitCode.ToString() + \"]\");
"
scr += " }
"
scr += " private static void SendEmail (string mSubject, string mMessage)
"
scr += " {
"
scr += " System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage();
"
scr += " System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient();
"
scr += " System.Net.NetworkCredential credential = new System.Net.NetworkCredential();
"
scr += " message.From = new System.Net.Mail.MailAddress (\"" + FROM_ADRESS + "\");
"
scr += " message.To.Add (\"" + TO_ADRESS + "\");
"
scr += " message.Subject = mSubject;
"
scr += " message.Body = mMessage;
"
scr += " credential.UserName = \"" + USER_NAME + "\";
"
scr += " credential.Password = \"" + USER_PASS + "\";
"
scr += " client.Host = \"" + SMTP_HOTS + "\";
"
scr += " client.Credentials = credential;
"
scr += " client.UseDefaultCredentials = true;
"
scr += " client.Port = " + (SMTP_PORT as string) + ";
"
scr += " client.Send(message);
"
scr += " }
"
scr += " private static void StartTimer (int mInterval)
"
scr += " {
"
scr += " pTimer = new System.Timers.Timer(mInterval);
"
scr += " pTimer.Enabled = true;
"
scr += " pTimer.Elapsed += ElapsedTimeEvent;
"
scr += " pTimer.Start();
"
scr += " }
"
scr += " private static void ElapsedTimeEvent (object mSource, ElapsedEventArgs mEvent)
"
scr += " {
"
scr += " if (!stopWatch.IsRunning)
"
scr += " {
"
scr += " pTimer.Interval = 500;
"
scr += " stopWatch.Start();
"
scr += " }
"
scr += " if (stopWatch.ElapsedMilliseconds >= pTimeout)
"
scr += " {
"
scr += " if ((Process.GetProcessById(pProc.Id)).Responding == false)
"
scr += " {
"
scr += " pTimeoutExit = true;
"
scr += " pProc.Kill();
"
scr += " }
"
scr += " stopWatch.Reset();
"
scr += " }
"
scr += " }
"
scr += "}
"
-- Build .EXE file
CreateConsoleAssembly scr @"C:\MaxGuard.exe"
)
EDIT:Updated the code to accept a new parameter (initial interval).
woooow! Tha’t really awsome! Thank youuu!
I love this so much. Jorge, Julius, thank you so much. That really makes sense.
Mike, if you ever use the last code I posted (or a modified version), would you please report back how successful it was, any bugs or unhandled situations you might find and any improvements would you like make?
Thank you!
i still don’t understand how all solutions shown above can help in situation when max is hanged, frozen, or crashed but its process was not stopped. it’s 90% of “crash” cases for me
There is no generic approach to solve this. Do you have a piece of code to reproduce what you mean?
If Process.Responding() does not work (which is quite probably), then there may be other solutions.
For example, this should hang Max (make it unresponsive) for some time.
for j = 1 to 2^27 while not keyboard.escPressed do()
If I launch Max using the Shell .EXE created with the code I previously posted, with an interval of 10 seconds and then run this loop in Max, it works well on my end, it kills Max and send me an email.
The only case I see it does not handle, is when an error message is displayed, as happen in many situations, but I suppose its a matter of working on it to find a workaround. After all, the code is just mean to be used as template, or to borrow some ideas from it.
EDIT: Added example.
sure, Jorge! this piece of code I will implement a bit later to my main script, and once connected, I will use it every day in my workflow, that’s why I will give feed back as soon as I have results.
Hi there,
I’ve successfully implemented the project from Jorge. I actually run it from a .net winforms application. I’ve also added a bit where, in case of a crash, the senddmp.exe process is killed automatically. That’s the popup which asks if you want to send feedback to autodesk.
In many cases, an error in 3dsMax results in an Application Error dialog. Max doesn’t exit, but it waits for somebody to click the ok or cancel button. I’ve added a dialogmonitor which clicks the cancel button. This results in max exiting and the console app picks that up.
It all works nicely, but it feels quite risky. Killing a process and listening for an error dialog somehow feels wrong. But still, I haven’t had any issues with it just yet.
It’s also worth noting that although Autodesk may not get back to you about individual CERs a large collection of similar CERs will push a bug fix up the priority.
Also if you have major issues I know a QA who will inspect your CER and give you more detailed information.
I’ve used VS to build my console app. It’s just a basic console app, no bells and whistles. I’ve taken out the messagebox or emailing function and I’ve added a method to kill the autodesk popup. This console app also doesn’t open a maxfile, but it opens 3dsmax and starts a script. I’m a little bit afraid of this, but it’s used by a single client in a fixed pipeline. So I’m quite certain it will be ok.
Here’s the Program.cs of the console app
using System;
using System.Diagnostics;
using System.Threading;
using System.Timers;
namespace CrashConsole
{
class Program
{
//hardcoded timing
private static int inactivityInterval = 20 * 60 * 1000; //20 minutes
private static int initialInterval = 5 * 60 * 1000; //5 minutes
private static int killDumpWait = 2 * 1000; //2 seconds
private static System.Timers.Timer processTimer;
private static Process theProcess;
private static Stopwatch stopWatch;
public static void Main(string[] mArgs)
{
Console.WriteLine("Welcome to CrashConsole");
Console.WriteLine("3ds Max will be started shortly
");
string maxPath = mArgs[0];
string scriptPath = mArgs[1];
Console.WriteLine("3dsMax: " + maxPath);
Console.WriteLine("Script: " + scriptPath);
//set up the process which will run the script
//we're listening for the exitcode of this process
ProcessStartInfo startInfo = new ProcessStartInfo();
string arguments = " -q -silent -u MAXScript \"" + scriptPath + "\"";
startInfo.Arguments = arguments;
startInfo.FileName = maxPath;
theProcess = Process.Start(startInfo);
//start a timer which checks for inactivity of the 3dsmax process
if (inactivityInterval > 0) StartTimer(initialInterval);
stopWatch = new Stopwatch();
theProcess.WaitForExit();
int exitCode = theProcess.ExitCode;
//if there has been an issue after exiting
//kill the autodesk popup after a crash
//any code other than 0 is an issue
if (exitCode != 0) EndDump(killDumpWait);
}
/// <summary>
/// Kill the senddmp process. this is the autodesk popup asking if you want to
/// send error info to them
/// </summary>
/// <param name="wait"></param>
private static void EndDump(int wait)
{
//Wait a little bit
Thread.Sleep(wait);
//kill the Send dump process if it's there
Process[] process = Process.GetProcessesByName("senddmp");
foreach (Process p in process) p.Kill();
}
/// <summary>
/// Start a timer and add an event to it
/// </summary>
/// <param name="startInterval">The interval to start with. Will be replaced later on to check more regular</param>
private static void StartTimer(int startInterval)
{
processTimer = new System.Timers.Timer(startInterval);
processTimer.Enabled = true;
processTimer.Elapsed += ElapsedTimeEvent;
processTimer.Start();
}
/// <summary>
/// This event, used by the timer, checks if the process is still responding
/// </summary>
/// <param name="mSource"></param>
/// <param name="mEvent"></param>
private static void ElapsedTimeEvent(object mSource, ElapsedEventArgs mEvent)
{
//if the stopwatch has been reset, or just not started yet, set a 5 sec interval of the timer and start
//the stopwatch again
if (!stopWatch.IsRunning)
{
processTimer.Interval = 5000;
stopWatch.Start();
}
//if we've waited longer than the inactivity interval, let's see
//if the 3dsMax process still responds
if (stopWatch.ElapsedMilliseconds >= inactivityInterval)
{
if ((Process.GetProcessById(theProcess.Id)).Responding == false) theProcess.Kill();
stopWatch.Reset();
}
}
}
}
This is the script I’m going to open. It contains a dialogmonitor which automatically closes the “Application error” dialog. It also contains a bit of code which should crash your max (tested in max 2017). So be very careful not to run this script in any precious, unsaved and unbackupped model. I’m calling this script CrashScript.ms
function fn_handleExceptionDialog =
(
/*<FUNCTION>
Description:
check for error dialogs and handle them
Arguments:
Return: true
</FUNCTION>*/
print "Checking error dialog"
local WindowHandle = DialogMonitorOPS.GetWindowHandle()
local theDialogName = UIAccessor.GetWindowText WindowHandle
case theDialogName of
(
"Application Error":
(
local theDialog = windows.getChildHWND 0 "Application Error"
local cancelHWND = windows.getChildHWND theDialog[1] "Cancel"
UIAccessor.PressButton cancelHWND[1]
)
)
true
)
--this dialogmonitor is here to press some buttons for the user
DialogMonitorOPS.unRegisterNotification id:#errormonitor
DialogMonitorOPS.RegisterNotification fn_handleExceptionDialog ID:#errormonitor
DialogMonitorOPS.Enabled = true
--crash max
-- http://forums.cgsociety.org/showthread.php?f=98&t=1292450&highlight=crash+max
with undo on
(
local poly = convertToPoly (editable_Mesh())
for n = 1 to 3 do polyOp.createVert poly [0,0,0]
polyOp.createPolygon poly #(1, 2, 3)
polyOp.setMapSupport poly 1 true
)
max undo
When you have the console app and this script you can run it like so
crashconsole.exe "C:\Program Files\Autodesk\3ds Max 2017\3dsmax.exe" crashscript.ms
This should open 3dsMax and execute the script. The script causes an application error. Its is closed by the dialogmonitor. Max then exits with an error code which is detected by the crashconsole. The CER dialog is opened and its process is killed automatically. Like I said, it’s a bit scary but it runs in a pipeline where we expect files to crash now and again. In that pipeline we do a lot of logging to eventually find out what’s going on. And of course Dave’s argument is very valid too.