Rare assert happening in my compenent (GMeter) Topic is solved

Are you writing your own components and need help with how to set them up or have questions about the components you are deriving from ? Ask them here.
Post Reply
aquawicket
Earned some good credits
Earned some good credits
Posts: 103
Joined: Sun Aug 05, 2007 5:49 am

Rare assert happening in my compenent (GMeter)

Post by aquawicket » Mon Feb 09, 2009 5:04 am

I've been getting this really rare assert when playing with my GMeter component. GMeter is a VU meter. It has a hit function to trigger the meter and a timer to do an animated falloff. I'm having a really hard time tracking down the issue causeing the assert.

Here's what the GMeter looks like
Image

Here's the assert error
Image

Code: Select all

//// GMeter.h /////////////////////////////////////////////////////////////////////////////
//
//
///////////////////////////////////////////////////////////////////////////////////////////

#ifndef GMETER_H
#define GMETER_H

#ifdef WIN32
	#include <windows.h>
#endif

#include <wx/window.h>
#include <wx/panel.h>
#include <wx/dcbuffer.h> 
#include <wx/msgdlg.h>
#include <wx/stopwatch.h>
#include <wx/timer.h>

///////////////////////////////
class GMeter: public wxPanel
{ 

public: 

	GMeter(wxWindow* parent, wxWindowID id,	const wxPoint& pos, const wxSize& size, bool falloff); 

	virtual ~GMeter(); 

	bool Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size); 

    void OnMouse(wxMouseEvent &event);
	void OnPaint(wxPaintEvent &WXUNUSED(event));
	void OnEraseBackground( wxEraseEvent& event ); 

    //Settings
	int SPEED1;
	int SPEED2;
	bool PEAK;
	int SPACING;

	int value;
	wxSize mySize;
	int falloff;
	void Hit(int val);

	wxStopWatch sw;
	long temp;
	long temp2;
	int falltemp;
	int peakfall;

	wxColour hitColor;
	wxColour peakcolor;
	wxCoord hitX1;
	wxCoord hitY1;
	wxCoord hitX2;
	wxCoord hitY2;
	
	wxMemoryDC *memDC;
	wxClientDC *cdc2;
	
	bool eraseFlag;
	bool hitFlag;

	DECLARE_EVENT_TABLE();

};


////////////////////////////
class GTimer: public wxTimer
{
public:

	GTimer();

	int meters;
	GMeter *m_pointers[100];
    void AddMeter(GMeter &m_pointer);
	void Notify();
};

#endif //GMeter_H

Code: Select all

//// GMeter.cpp ///////////////////////////////////////////////////////////
//
//    All wxDC work needs to be moved into the Paint event.  Mac restricts
//    any ablility to "dual access" the window with paint and DC's.
//      We will use wxMemoryDc and blit in the paint event.
//      This still needs to be done in the Notify() function. 
////////////////////////////////////////////////////////////////////////////

#include "GMeter.h" 

static GTimer timer;

///////////////////////////////////
BEGIN_EVENT_TABLE(GMeter, wxPanel) 
	EVT_MOUSE_EVENTS (GMeter::OnMouse)
	EVT_PAINT (GMeter::OnPaint)
    //EVT_ERASE_BACKGROUND(GMeter::OnEraseBackground) 
END_EVENT_TABLE() 

///////////////////////////////////////////////////////////////////////////////////////////////////
GMeter::GMeter(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, bool falloff)
{ 
	// SETTINGS
	SPEED1 = 2;
	SPEED2 = 100;
	PEAK = 1;
	SPACING = 5;
	
	hitFlag = false;
	eraseFlag = false;
	
	mySize = size;	

#ifndef LINUX
	SetBackgroundStyle(wxBG_STYLE_CUSTOM);
#endif //LINUX

	(void)Create(parent, id, pos, size); 

#ifndef MAC
	//Falloff painting not ready on MAC
	if(falloff){
		timer.AddMeter(*this);  //add meter to the timer event
	}
#endif //MAC
} 

/////////////////
GMeter::~GMeter() 
{ 
	delete memDC;
	delete cdc2;
	
	//destroy the meter in the timer class too.
	for(int i=0; i<=timer.meters; i++){
		if(timer.m_pointers[i] == this){
			
			while(i <= timer.meters){
				timer.m_pointers[i] = timer.m_pointers[i+1];
				i++;
			}

			timer.meters--;
			break;
		}
	}
} 

////////////////////////////////////////////////////////////////////////////////////////////////////////
bool GMeter::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size) 
{ 
	if(!wxPanel::Create(parent, id, pos, size, wxNO_BORDER, wxT(""))) 
		return false; 

	memDC = new wxMemoryDC();
	
	cdc2 = new wxClientDC(this);
	SetBackgroundColour(wxColour(0,0,0));
	eraseFlag = true;
	Refresh();

	return true; 
} 

////////////////////////////////////////////////////
void GMeter::OnPaint(wxPaintEvent &WXUNUSED(event)) 
{ 
	wxBufferedPaintDC dc(this); 

	if(eraseFlag){
		dc.SetBackground(wxColour(0, 0, 0)); 
		dc.SetBackgroundMode(wxSOLID); 
		dc.Clear(); 
		dc.SetBrush(*wxTRANSPARENT_BRUSH); 
		eraseFlag = false;
	}
	
	if(hitFlag){
		dc.Blit(0, 0, mySize.x, mySize.y, memDC, 0, 0);
		hitFlag = false;
	}

	else{
		dc.SetBackground(wxColour(0, 0, 0)); 
		dc.SetBackgroundMode(wxSOLID); 
		dc.Clear(); 
		dc.SetBrush(*wxTRANSPARENT_BRUSH); 
	}

} 

/////////////////////////////////////////////////////
void GMeter::OnEraseBackground( wxEraseEvent& event )
{

}

/////////////////////////
void GMeter::Hit(int val)
{

	memDC->SelectObject(wxBitmap(mySize.x, mySize.y, -1)); //The ASSERT starts here sometimes

	sw.Start();
	falloff = (val * mySize.y) / 100;

	falltemp = falloff;
	peakfall = falloff;
	value = val;

	int position = (mySize.y * value) / 100;
	int b = SPACING -1;

	for(int i=0; i<position; i++){
		int percent = ((i * 100) / mySize.y);

		int color1 = (255 * percent) / 100 * 2;
		if(percent > 50){color1 = 255;}

		int color2 = 255 - ((255 * percent) / 100 * 2);

		if(percent < 51){color2 = 255;}
		
		if(i != b || i == position-1){
			memDC->SetPen(wxColour(color1,color2,0)); //The ASSERT starts here sometimes
			memDC->DrawLine(0, mySize.y -1 -i, mySize.x, mySize.y -1 -i);
		}
			
		else{b=b+SPACING;}
		
		//store the peak color
		if(i == position-1){peakcolor = wxColour(color1, color2, 0);}
	}
	
	hitFlag = true;
	Refresh();
}

/////////////////////////////////////////
void GMeter::OnMouse(wxMouseEvent &event) 
{ 
	if(event.LeftDown()){
		//GetParent()->ProcessEvent(event); //Weird parent window displacement when attempting to use meter to drag window
	}
	if(event.RightUp())
	{
		GetParent()->ProcessEvent(event); //Let event bleed through to the parent.
	}
}



////////////////////////////
GTimer::GTimer() : wxTimer()
{
	Start(1,false);
}

////////////////////////////////////////
void GTimer::AddMeter(GMeter &m_pointer)
{
	m_pointers[meters] = &m_pointer;
	meters++;
}

/////////////////////////////////////////
void GTimer::Notify()
{
	
	//process falloffs
	if(meters > 0){
		for(int i=0; i<meters; i++){
			if(m_pointers[i]->falloff > 0){
				
				m_pointers[i]->temp = m_pointers[i]->sw.Time();
				//if(m_pointers[i]->temp > m_pointers[i]->mySize.y){m_pointers[i]->temp = 15;}

				if(m_pointers[i]->falltemp > 0){

					m_pointers[i]->cdc2->SetPen(wxColour(0, 0, 0));
					m_pointers[i]->cdc2->SetBrush(wxColour(0, 0, 0));

                    m_pointers[i]->cdc2->DrawRectangle(0, m_pointers[i]->mySize.y +m_pointers[i]->PEAK -m_pointers[i]->falloff, m_pointers[i]->mySize.x, m_pointers[i]->temp / m_pointers[i]->SPEED1);
					
                    m_pointers[i]->falltemp =  m_pointers[i]->falloff - (m_pointers[i]->temp  / m_pointers[i]->SPEED1);		
				}

				else if(m_pointers[i]->peakfall > -m_pointers[i]->SPEED2 && m_pointers[i]->PEAK){
					m_pointers[i]->cdc2->SetPen(wxColour(0, 0, 0));
					m_pointers[i]->cdc2->DrawLine(0, m_pointers[i]->mySize.y -m_pointers[i]->peakfall, m_pointers[i]->mySize.x, m_pointers[i]->mySize.y -m_pointers[i]->peakfall);
					m_pointers[i]->peakfall = m_pointers[i]->peakfall - (m_pointers[i]->temp / m_pointers[i]->SPEED2);
					m_pointers[i]->cdc2->SetPen(m_pointers[i]->peakcolor);
					m_pointers[i]->cdc2->DrawLine(0, m_pointers[i]->mySize.y -m_pointers[i]->peakfall, m_pointers[i]->mySize.x, m_pointers[i]->mySize.y -m_pointers[i]->peakfall);
				}

				else{
					m_pointers[i]->falloff = 0;
					m_pointers[i]->sw.Pause();
				}
			}
		}
    }
}

illnatured
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 234
Joined: Mon May 08, 2006 12:31 pm
Location: Krakow, Poland

Post by illnatured » Mon Feb 09, 2009 9:52 am

This is not a rare assert. Bad reference counting is a common problem. This message usually appears when you try to release object that is already deleted. You can use a debugger to fix this issue.

aquawicket
Earned some good credits
Earned some good credits
Posts: 103
Joined: Sun Aug 05, 2007 5:49 am

Post by aquawicket » Mon Feb 09, 2009 10:37 am

illnatured,

It makes complete sense what your saying, although the assert is not consistent. I only get the assert 1 out of 500 times on average. So it does make it hard to pin point the problem.

As you can see in the code, I don't release anything until the destruction, which is not called until the programs end. And I get a nice 0x00 cleanup on exit. It's the bug during live time that troubles me. Every time the assert happens, it's in my wxMemoryDc "memDC" variable.

Lets say I call GMeter::Hit thousands of times a second. Maybe even, dare I say, from different threads at the same exact time.

The real question here is, how do I make this class bullet-proof no matter what pings the GMeter::Hit function.

And thank you very much for your reply :)

tan
Moderator
Moderator
Posts: 1471
Joined: Tue Nov 14, 2006 7:58 am
Location: Saint-Petersburg, Russia

Post by tan » Mon Feb 09, 2009 11:31 am

Hi,
try replace

Code: Select all

        memDC->SelectObject(wxBitmap(mySize.x, mySize.y, -1)); //The ASSERT starts here sometimes
to

Code: Select all

        memDC->SelectObjectAsSource(wxBitmap(mySize.x, mySize.y, -1));
OS: Windows XP Pro
Compiler: MSVC++ 7.1
wxWidgets: 2.8.10

aquawicket
Earned some good credits
Earned some good credits
Posts: 103
Joined: Sun Aug 05, 2007 5:49 am

Post by aquawicket » Mon Feb 09, 2009 2:41 pm

Thank you very much for the reply.
I Still get the Assert after a while the same as I did before.
Only now it happens on the SelectObjectAsSource() function just the same.
And as I said before, it only happens about 0.5% of the time.

I can tell that it always happens with the wxMemoryDC variable.

Is there a way to make sure the variable is valid to work with before attempting action? Any ideas how get my own assert tests in before it falls deep into wx code?

I've tried if(memDC->Ok()) but it passes and the Assert still happens.

aquawicket
Earned some good credits
Earned some good credits
Posts: 103
Joined: Sun Aug 05, 2007 5:49 am

Post by aquawicket » Mon Feb 09, 2009 2:56 pm

Ok, I've been able to expose the bug.
It seems that my threads are calling this function at the same time to cause the bug, therefore fighting over memDC.

using the "allreadyInThisFunction" variable works, but I'm now looking for a better way to make this safe to multiple threads.

And once again, thank you to all that have responded. :)
You guys got me thinking on the right path to pin point the issue :)


Code: Select all

/////////////////////////
void GMeter::Hit(int val)
{
	if(!allreadyInThisFunction){
		allreadyInThisFunction = true;
	        memDC->SelectObject(wxBitmap(mySize.x, mySize.y, -1)); 

	        sw.Start();
	        falloff = (val * mySize.y) / 100;

	        falltemp = falloff;
	        peakfall = falloff;
	        value = val;

	        int position = (mySize.y * value) / 100;
	        int b = SPACING -1;

	        for(int i=0; i<position; i++){
		        int percent = ((i * 100) / mySize.y);

		        int color1 = (255 * percent) / 100 * 2;
		        if(percent > 50){color1 = 255;}

		        int color2 = 255 - ((255 * percent) / 100 * 2);

		        if(percent < 51){color2 = 255;}
		
		        if(i != b || i == position-1){
			         memDC->SetPen(wxColour(color1,color2,0)); 
			         memDC->DrawLine(0, mySize.y -1 -i, mySize.x, mySize.y -1 -i);
		        }
			
		        else{b=b+SPACING;}
		
		        //store the peak color
		        if(i == position-1){peakcolor = wxColour(color1, color2, 0);}
	        }
	
	        hitFlag = true;
	        Refresh();

	        allreadyInThisFunction = false;
                //Is there a chance that a thread can slip in a call to this function while we are here?
                //And at this point, does it matter if it does?
	}

        
	else{
		wxMessageBox("where allready in this function, this is where the assert would happen");
	}
}

catalin
Moderator
Moderator
Posts: 1521
Joined: Wed Nov 12, 2008 7:23 am
Location: Romania

Post by catalin » Mon Feb 09, 2009 11:44 pm

aquawicket wrote:

Code: Select all

                //Is there a chance that a thread can slip in a call to this function while we are here?
Yes
aquawicket wrote:

Code: Select all

                //And at this point, does it matter if it does?
As much as it did till now..
aquawicket wrote:I'm now looking for a better way to make this safe to multiple threads.

Code: Select all

/////////////////////////
void GMeter::Hit(int val)
{
        if(!allreadyInThisFunction){
                allreadyInThisFunction = true;
                memDC->SelectObject(wxBitmap(mySize.x, mySize.y, -1)); 

                /* whatever is already there */

                Refresh();
                allreadyInThisFunction = false;
                //Is there a chance that a thread can slip in a call to this function while we are here?
                //And at this point, does it matter if it does?
        }

       
        else{
                wxMessageBox("where allready in this function, this is where the assert would happen");
        }
}
I did not try to understand all the code snippets in this thread but I think you just want to have the contents of GMeter::Hit thread safe.


So try this:
Create a wxCriticalSection variable member of GMeter. Then you can just use it with a wxCriticalSectionLocker in your current code.

Code: Select all

void GMeter::Hit(int val)
{
    wxCriticalSectionLocker locker( m_critSection );

    memDC->SelectObject(wxBitmap(mySize.x, mySize.y, -1));

    /* whatever is already there */

    Refresh();
}
HTH

aquawicket
Earned some good credits
Earned some good credits
Posts: 103
Joined: Sun Aug 05, 2007 5:49 am

Post by aquawicket » Tue Feb 10, 2009 1:43 am

Perfect, Thank you very much :wink:

Post Reply