BUG: Editor crashes when monitor turned off

Started by roltemurto, Today at 10:39:57 AM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

roltemurto

Hello again WillLem,

Sorry for bombarding the forum board lately, but I got a new bug report.
Below I am attaching the .NET Framework error pop-up. The log file doesn't have anything up to date.
This happened after I turned my monitor off for an hour (at least this is what I am suspecting of what caused it).
When I turned it back on these errors were popped up;

Instead of this dialog box, JIT debugging would be invoked
For more details, see the end of this message.

************** Exception Text **************
System.InvalidOperationException: Bitmap region is already locked.
at: System.Drawing.Bitmap.LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format, BitmapData bitmapData)
at: System.Drawing.Bitmap.LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format)
at: SLXEditor.BmpModify.DrawOn(Bitmap origBmp, Bitmap newBmp, Point pos, Func`3 doDrawThisPixel, Byte alpha)
at: SLXEditor.BmpModify.DrawOn(Bitmap origBmp, Bitmap newBmp)
at: SLXEditor.Renderer.CreateLevelImageFromLayers(String dragNewPieceKey)
at: SLXEditor.Renderer.CombineLayers(String dragNewPieceKey)
at: SLXEditor.SLXEditForm.ResetLevelImage()
at: SLXEditor.SLXEditForm.NLEditForm_Resize(Object sender, EventArgs e)
at: System.EventHandler.Invoke(Object sender, EventArgs e)
at: System.Windows.Forms.Control.OnResize(EventArgs e)
at: System.Windows.Forms.Form.OnResize(EventArgs e)
at: System.Windows.Forms.Control.OnSizeChanged(EventArgs e)
at: System.Windows.Forms.Control.UpdateBounds(Int32 x, Int32 y, Int32 width, Int32 height, Int32 clientWidth, Int32 clientHeight)
at: System.Windows.Forms.Control.UpdateBounds()
at: System.Windows.Forms.Control.WmWindowPosChanged(Message& m)
at: System.Windows.Forms.Control.WndProc(Message& m)
at: System.Windows.Forms.ScrollableControl.WndProc(Message& m)
at: System.Windows.Forms.ContainerControl.WndProc(Message& m)
at: System.Windows.Forms.Form.WmWindowPosChanged(Message& m)
at: System.Windows.Forms.Form.WndProc(Message& m)
at: System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at: System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at: System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

************** Loaded Assemblies **************
mscorlib
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4515.0 built by: NET48REL1LAST_C
CodeBase: file:///C:/Windows/Microsoft.NET/Framework/v4.0.30319/mscorlib.dll

SLXEditor
Assembly Version: 3.0.3.0
Win32 Version: 3.0.3.0
CodeBase: file:///X:/YEKELEMELER/OYUN/Lemmings/NeoLemmix_V12.14.0/SuperLemmix%203.0.2/SLXEditor.exe

System.Windows.Forms
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4550.0 built by: NET48REL1LAST_C
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Windows.Forms/v4.0_4.0.0.0__b77a5c561934e089/System.Windows.Forms.dll

System
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4536.0 built by: NET48REL1LAST_C
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System/v4.0_4.0.0.0__b77a5c561934e089/System.dll

System.Drawing
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4390.0 built by: NET48REL1LAST_C
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Drawing/v4.0_4.0.0.0__b03f5f7f11d50a3a/System.Drawing.dll

System.Core
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4590.0 built by: NET48REL1LAST_B
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Core/v4.0_4.0.0.0__b77a5c561934e089/System.Core.dll

System.Configuration
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4190.0 built by: NET48REL1LAST_B
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Configuration/v4.0_4.0.0.0__b03f5f7f11d50a3a/System.Configuration.dll

System.Xml
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4084.0 built by: NET48REL1
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Xml/v4.0_4.0.0.0__b77a5c561934e089/System.Xml.dll

Accessibility
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4084.0 built by: NET48REL1
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/Accessibility/v4.0_4.0.0.0__b03f5f7f11d50a3a/Accessibility.dll

mscorlib.resources
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4084.0 built by: NET48REL1
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/mscorlib.resources/v4.0_4.0.0.0_tr_b77a5c561934e089/mscorlib.resources.dll

System.Drawing.resources
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4084.0 built by: NET48REL1
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Drawing.resources/v4.0_4.0.0.0_tr_b03f5f7f11d50a3a/System.Drawing.resources.dll

System.Windows.Forms.resources
Assembly Version: 4.0.0.0
Win32 Version: 4.8.4084.0 built by: NET48REL1
CodeBase: file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Windows.Forms.resources/v4.0_4.0.0.0_tr_b77a5c561934e089/System.Windows.Forms.resources.dll

************** JIT Debugging **************
To enable JIT debugging, the jitDebugging value must be set in the
system.windows.forms section of the application's or machine's (machine.config)
.config file. The application must also be compiled with debugging
enabled.

For example:
<configuration> <system.windows.forms jitDebugging="true" /> </configuration>

When JIT debugging is enabled, any unhandled exception
will be sent to the JIT debugger registered on the computer
rather than being handled in this dialog box.

Luckily the editor DID allow me to save the map before exiting.
The reason I'm suspecting the monitor is due to the fact that I am using displayport for connection and I believe this connection causes resolution change during turn off and wake up.

WillLem

It appears to have been triggered by the form resizing. Your instincts are likely correct: if the resolution change occurs on wake, that could trigger a form resize.

Not sure exactly why the exception occurred though. Maybe more resolution-specific guards need to be in place.

Good to know that no data was lost, that's fortunate.

roltemurto

Quote from: WillLem on Today at 03:45:11 PMNot sure exactly why the exception occurred though. Maybe more resolution-specific guards need to be in place.

I hope I don't overstep my boundaries by doing this but I just want to be helpful.
So I gave the errors to an AI and explained the situation.
Here's how it diagnosed it and came up with possible solutions;


Diagnosis

Root cause: System.InvalidOperationException: Bitmap region is already locked.

The call stack is:

NLEditForm_Resize → ResetLevelImage → CombineLayers → CreateLevelImageFromLayers → BmpModify.DrawOn → Bitmap.LockBits
When the monitor is turned off via DisplayPort, Windows triggers a resolution/display change, which causes the WinForms window to receive a WM_WINDOWPOSCHANGED message. This fires OnResizeNLEditForm_Resize. The problem is that the renderer was already in the middle of a rendering operation (with a bitmap locked via LockBits) when this resize event fired — either from a concurrent render or from a prior render that didn't properly call UnlockBits before the resize interrupted it.

This is a re-entrancy / double-lock bug: Bitmap.LockBits is called on a bitmap that still has an active lock, either because:
  • A previous render was interrupted mid-flight and UnlockBits was never called (e.g. due to an earlier exception), or
  • The resize event fires while a render is already running (re-entrant call).



Solution

There are two complementary fixes that should both be applied.

Fix 1 — Re-entrancy guard on ResetLevelImage

Prevent NLEditForm_Resize from triggering a render if one is already in progress:

// In SLXEditForm:
private bool _isRendering = false;

private void ResetLevelImage()
{
    if (_isRendering) return;  // Guard against re-entrant calls
    _isRendering = true;
    try
    {
        // ... existing ResetLevelImage body ...
    }
    finally
    {
        _isRendering = false;
    }
}

This is the most important fix. It prevents a resize event from launching a new render while one is already running.



Fix 2 — Ensure UnlockBits is always called in BmpModify.DrawOn

The existing DrawOn method almost certainly does LockBits without a try/finally guarantee on UnlockBits. If any exception occurs mid-render, the bitmap stays locked forever for the lifetime of the object. Fix the locking pattern:

// In BmpModify.DrawOn — wrap the LockBits/UnlockBits pair in try/finally:
BitmapData bmpData = null;
try
{
    bmpData = origBmp.LockBits(rect, ImageLockMode.ReadOnly, origBmp.PixelFormat);
    // ... pixel operations ...
}
finally
{
    if (bmpData != null)
        origBmp.UnlockBits(bmpData);
}

Do the same for newBmp if it is also locked in that method. This ensures that even if an exception fires partway through a render, the lock is always released and future renders won't see a pre-locked bitmap.



Fix 3 (optional hardening) — Debounce resize events

Resize events can fire dozens of times per second during a resolution change. Even with the re-entrancy guard, it is worth debouncing to avoid hammering the renderer immediately after wake:

private System.Windows.Forms.Timer _resizeDebounce;

private void NLEditForm_Resize(object sender, EventArgs e)
{
    if (_resizeDebounce == null)
    {
        _resizeDebounce = new System.Windows.Forms.Timer { Interval = 150 };
        _resizeDebounce.Tick += (s, _) =>
        {
            _resizeDebounce.Stop();
            ResetLevelImage();
        };
    }
    _resizeDebounce.Stop();
    _resizeDebounce.Start();
}

This collapses a burst of resize events into a single render call 150ms after the last one.



Summary

FixWhat it addresses
Re-entrancy guard (_isRendering)Stops a second render from starting while one is in progress
try/finally around LockBitsGuarantees unlock even if a render throws mid-way
Resize debounceReduces needless render thrash during resolution changes

Fix 1 alone would have prevented this specific crash. Fix 2 prevents the "permanently locked bitmap" failure mode that makes Fix 1 necessary in the first place. Fix 3 is polish.