Discussion:
METAFILE Memory Leak help!
(too old to reply)
Jim Pike
2011-01-20 16:44:15 UTC
Permalink
I am new to GdiPlus and METAFILEs. We have a Sharepoint web part,
that basically calls up an ASPX web page (C#) that calls a remote dll
web service (C++) that then calls the engine dlls (C++) that produces
an image of a chart and then displays it in the web part.

First here is the main method in question. It is in the engine/main
dll code. The reason I think I have a memory leak is this. In a
single user scenario the system works flawlessly. However when I hook
up HP LoadRunner with 25 users going I get problems. On the line HDC
hDC = CreateEnhMetaFile(hdcRef, NULL, &rect, NULL); I get back null
and when I call GetLastError() I get ERROR_NOT_ENOUGH_MEMORY. This
makes me think somewhere I am leaking memory that in a multi-user load
test scenario eventually piles up and over. Does that sound like a
reasonable conclusion?

STDMETHODIMP CChartGen::GetChart(BSTR bstrFile, imgFormat iFormat,
long lChartID, long lElemID, long lPeriodID, long lUnit, BSTR
bstrFlags, long lWidth, long lHeight)
{
if( (lElemID < -1) || // <= 0 indicates summary query
(lPeriodID < -1) || // <= 0 indicates cross-contract summary
query
(lUnit <= 0) ||
(lChartID <= 0 ) )
{
return E_INVALIDARG;
}

if( m_lExtraID != 0 )
{
if( m_lExtraID != lChartID )
return E_INVALIDARG;
m_lExtraID = 0;
}

if( (iFormat == imgFormatGIF) && !m_bEnableGIF )
{
return E_INVALIDARG;
}

// special palette for background colors
RGBQUAD pal1[] =
{
0,0,0,0, // Black
0, 0, 128, 0, // Maroon
0, 128, 0, 0, // Green
0, 128, 128, 0, // Olive
128, 0, 0, 0, // Navy
128, 0, 128, 0, // Purple
128, 128, 0, 0, // Teal
108, 108, 108, 0, // Gray
182, 182, 182, 0, // Silver
0, 0, 255, 0, // Red
128, 255, 128, 0, // Lime (modified)
128, 255, 255, 0, // Yellow (modified)
255, 0, 0, 0, // Blue
128, 128, 255, 0, // Fuschia (modified)
255, 192, 128, 0, // Aqua (modified)
255, 255, 255, 0, // White
};

// special colors for IVAC chart
RGBQUAD pal2[] =
{
0,0,0,0, // Black
0, 0, 128, 0, // Maroon
0, 128, 0, 0, // Green
0, 128, 128, 0, // Olive
128, 0, 0, 0, // Navy
128, 0, 128, 0, // Purple
128, 128, 0, 0, // Teal
108, 108, 108, 0, // Gray
182, 182, 182, 0, // Silver
0, 0, 255, 0, // Red
0, 255, 0, 0, // Lime
0, 255, 255, 0, // Yellow
255, 0, 0, 0, // Blue
255, 0, 255, 0, // Fuschia
255, 255, 0, 0, // Aqua
255, 255, 255, 0, // White
};

if( !LoadVFL() )
return E_FAIL;

m_lChartID = lChartID;
m_lElemID = lElemID;
m_lPeriodID = lPeriodID;
m_lUnitID = lUnit;
m_bstrFlags = bstrFlags;
m_lWidth = lWidth;
m_lHeight = lHeight;

CChart& ch = m_charts.GetInit(lChartID-1);
BYTE r, g, b;
LPCTSTR lpszExopt = ch.GetExopt();

// lChartID gives 1-based position in WSCUSTOM.xml
// id is the id attribute in of that chart
long id = ch.GetID();
int flags = ch.GetFlags();

bool bSetBackground = (id > CH_FUND && lpszExopt[0]);
if( (flags & chartGaugeType) == 0 )
bSetBackground = bSetBackground && (strchr((const char*)m_bstrFlags,
'o') == 0);

if( bSetBackground )
{
to_rgb(lpszExopt, r, g, b);
pal1[14].rgbRed = r;
pal1[14].rgbGreen = g;
pal1[14].rgbBlue = b;
}

CDataSource src;
CSession session;

HRESULT hr = InitDataSource(src);
if( hr == S_OK )
hr = session.Open(src);
if( hr == S_OK )
hr = LoadColumns(session);
if( hr == S_OK )
hr = CheckDateFormat(m_login.sConn.c_str(), session);
if( FAILED(hr) ) return hr;

if( m_lElemID > 0 && m_lPeriodID == 0 )
{
char fmt[256], buf[256];

dbItemID item;
LoadString(_Module.m_hInst, IDS_QRY_LATESTPER, fmt, sizeof(fmt));
wsprintf(buf, fmt, m_lElemID, m_lUserID);
hr = LoadItemID(session, buf, item);
if(FAILED(hr)) return hr;
m_lPeriodID = item.id;
}

HDC hdcRef = GetDC(NULL);
int iWidthMM = GetDeviceCaps(hdcRef, HORZSIZE);
int iHeightMM = GetDeviceCaps(hdcRef, VERTSIZE);
int iWidthPels = GetDeviceCaps(hdcRef, HORZRES);
int iHeightPels = GetDeviceCaps(hdcRef, VERTRES);

RECT rect = { 0 };
rect.right = (m_lWidth * iWidthMM * 100)/iWidthPels;
rect.bottom = (m_lHeight * iHeightMM * 100)/iHeightPels;

HDC hDC = CreateEnhMetaFile(hdcRef, NULL, &rect, NULL);
if( hDC == NULL )
{
ReleaseDC(NULL, hdcRef);
return E_FAIL;
}

HBRUSH hBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);
if( hBrush )
{
FillRect( hDC, &rect, hBrush );
DeleteObject(hBrush);
}

m_bCanZoom = true;
id = ch.GetID();

switch( id )
{
case CH_CSVARTRND:
m_bCanZoom = false;
hr = GenerateCSVarTrendsChart( session, hDC );
break;

case CH_CONTPERF:
m_bCanZoom = false;
hr = GenerateContPerfChart( session, hDC );
break;

case CH_ELPERF_CUM:
case CH_ELPERF_CUR:
hr = GenerateElemPerfChart( session, hDC );
break;

case CH_TPANAL_CUM:
case CH_TPANAL_CUR:
hr = GenerateTimePhaseAnalysisChart( session, hDC );
break;

case CH_MANPOWER:
m_bCanZoom = false;
hr = GenerateManpowerChart( session, hDC );
break;

case CH_BULLSEYE:
hr = GenerateBullsEyeChart( session, hDC );
break;

case CH_CSVAR:
m_bCanZoom = false;
hr = GenerateCSVarChart(session, hDC);
break;

case CH_ARMYCS:
m_bCanZoom = false;
hr = GenerateArmyCSVarChart( session, hDC );
break;

case CH_OTB:
m_bCanZoom = false;
hr = GenerateOtbChart( session, hDC );
break;

case CH_AGBULL:
hr = GenerateAgBullsEyeChart( session, hDC );
break;

case CH_VAC:
m_bCanZoom = false; // controlled by WSCUSTOM, always load
hr = GenerateVacChart( session, hDC );
break;

case CH_BLANAL:
m_bCanZoom = false;
hr = GenerateBlAnalChart( session, hDC );
break;

case CH_FUND:
m_bCanZoom = false;
hr = GenerateFundChart( session, hDC );
break;

case CH_DCMA_CRITPATHLENINDX:
hr = GenerateDCMACriticalPathLengthIndexChart( session, hDC );
m_bCanZoom = false;
break;

case CH_DCMA_BEI_COMPLETES:
case CH_DCMA_BEI_STARTS:
hr = GenerateDCMABaselineExecutionIndexChart( session, hDC );
m_bCanZoom = false;
break;

case CH_DCMA_CSVARTRENDS:
hr = GenerateDCMACSVarTrendsChart( session, hDC );
m_bCanZoom = false;
break;

case CH_DCMA_CTREACVAL:
hr = GenerateDCMAContEacValidityChart( session, hDC );
m_bCanZoom = false;
break;

case CH_DCMA_CSVARTRENDS_OTB:
hr = GenerateDCMAOtbChart( session, hDC );
m_bCanZoom = false;
break;

case CH_DCMA_CTREACVAL_OTB:
hr = GenerateDCMAContEacValidityOTBChart( session, hDC );
m_bCanZoom = false;
break;

default:
if( (flags & chartGaugeType) == 0 )
{
hr = GenerateAnalysisChart( session, hDC );
}
else
{
m_bCanZoom = false;
hr = GenerateGaugeChart( session, hDC );
}
break;
}

HENHMETAFILE hemf = CloseEnhMetaFile(hDC);
if(NULL == hemf)
{
hr = E_FAIL;
ReleaseDC(NULL, hdcRef);
return hr;
}

if( hr == S_OK )
{

RGBQUAD* pPalette = 0;
if( iFormat != imgFormatJPG )
pPalette = (id != CH_VAC) ? pal1 : pal2;
hr = GetEMFType(hemf, m_lWidth, m_lHeight, COLE2T(bstrFile),
iFormat, pPalette );

}
DeleteEnhMetaFile(hemf);

ReleaseDC(NULL, hdcRef);

return hr;
}

And here is the method "GetEMFType" that is called.

HRESULT GetEMFType(HENHMETAFILE hemf, long lWidth, long lHeight,
LPCSTR lpszFile, imgFormat iFormat, RGBQUAD* pPalette)
{
HRESULT hr = S_OK;

#ifdef _GDIPLUS

if (FAILED(InitializeGdiPlus()))
return hr;

GUID ImageType[5] = {
Gdiplus::ImageFormatEMF,
Gdiplus::ImageFormatBMP,
Gdiplus::ImageFormatJPEG,
Gdiplus::ImageFormatPNG,
Gdiplus::ImageFormatGIF
};

Gdiplus::Status status;

CLSID clsidEncoder = CLSID_NULL;

hr = GetGdiPlusImageCodec(ImageType[iFormat], clsidEncoder);
if (SUCCEEDED(hr))
{
USES_CONVERSION_EX;
LPCWSTR pwszFileName = T2CW_EX( lpszFile,
_ATL_SAFE_ALLOCA_DEF_THRESHOLD );
if ( NULL != pwszFileName )
{
Gdiplus::Metafile metafile(hemf, FALSE);
status = metafile.Save(pwszFileName, &clsidEncoder, NULL);

if( status != Gdiplus::Ok )
{
hr = E_FAIL;
}
}
else
hr = E_OUTOFMEMORY;
}

return hr;
}

Any help woudl be GREATLY appreciated. Even just a point in the right
direction.
David Lowndes
2011-01-24 10:45:53 UTC
Permalink
Post by Jim Pike
First here is the main method in question. It is in the engine/main
dll code. The reason I think I have a memory leak is this. In a
single user scenario the system works flawlessly. However when I hook
up HP LoadRunner with 25 users going I get problems. On the line HDC
hDC = CreateEnhMetaFile(hdcRef, NULL, &rect, NULL); I get back null
and when I call GetLastError() I get ERROR_NOT_ENOUGH_MEMORY. This
makes me think somewhere I am leaking memory that in a multi-user load
test scenario eventually piles up and over. Does that sound like a
reasonable conclusion?
No. If you have a memory leak, it'd leak in the single user scenario
as well.

I'd have thought from what you describe that it's more likely that
you're just requiring a lot of memory and with concurrent users you're
hitting the limit. What happens with the same test with 1, 2, 5, 10,
15, 20 users?
Post by Jim Pike
RGBQUAD pal1[] =
RGBQUAD pal2[] =
If those don't need to be writeable, I'd make them static const,
otherwise your code is creating them every time - not that it's your
immediate problem.

Dave
Jim Pike
2011-01-25 16:43:39 UTC
Permalink
Post by David Lowndes
Post by Jim Pike
First here is the main method in question. It is in the engine/main
dll code. The reason I think I have a memory leak is this.  In a
single user scenario the system works flawlessly. However when I hook
up HP LoadRunner with 25 users going I get problems. On the line HDC
hDC = CreateEnhMetaFile(hdcRef, NULL, &rect, NULL); I get back null
and when I call GetLastError() I get ERROR_NOT_ENOUGH_MEMORY. This
makes me think somewhere I am leaking memory that in a multi-user load
test scenario eventually piles up and over. Does that sound like a
reasonable conclusion?
No. If you have a memory leak, it'd leak in the single user scenario
as well.
I'd have thought from what you describe that it's more likely that
you're just requiring a lot of memory and with concurrent users you're
hitting the limit. What happens with the same test with 1, 2, 5, 10,
15, 20 users?
Post by Jim Pike
   RGBQUAD pal1[] =
   RGBQUAD pal2[] =
If those don't need to be writeable, I'd make them static const,
otherwise your code is creating them every time - not that it's your
immediate problem.
Dave
Using LoadRunner with as little as 1 or 2 users will make it repeat.
Or at least it would. I found some other errors (not directly related
to this) that fixed some of the multiuser testing, but I have seen the
chart still fail on as little as one user just hitting it fast and
with proper timing.
Jim Pike
2011-01-25 17:46:22 UTC
Permalink
Post by Jim Pike
Post by David Lowndes
Post by Jim Pike
First here is the main method in question. It is in the engine/main
dll code. The reason I think I have a memory leak is this.  In a
single user scenario the system works flawlessly. However when I hook
up HP LoadRunner with 25 users going I get problems. On the line HDC
hDC = CreateEnhMetaFile(hdcRef, NULL, &rect, NULL); I get back null
and when I call GetLastError() I get ERROR_NOT_ENOUGH_MEMORY. This
makes me think somewhere I am leaking memory that in a multi-user load
test scenario eventually piles up and over. Does that sound like a
reasonable conclusion?
No. If you have a memory leak, it'd leak in the single user scenario
as well.
I'd have thought from what you describe that it's more likely that
you're just requiring a lot of memory and with concurrent users you're
hitting the limit. What happens with the same test with 1, 2, 5, 10,
15, 20 users?
Post by Jim Pike
   RGBQUAD pal1[] =
   RGBQUAD pal2[] =
If those don't need to be writeable, I'd make them static const,
otherwise your code is creating them every time - not that it's your
immediate problem.
Dave
Using LoadRunner with as little as 1 or 2 users will make it repeat.
Or at least it would. I found some other errors (not directly related
to this) that fixed some of the multiuser testing, but I have seen the
chart still fail on as little as one user just hitting it fast and
with proper timing.- Hide quoted text -
- Show quoted text -
I don't know if this will help but this is what LoadRunner looks like:

Loading Image...

As you can see, the average response time gets worse and worse until
it crashes around the 35 minute mark. The rest of the web parts
continue to work, but the chart gets the error after this point.
David Lowndes
2011-01-26 00:22:09 UTC
Permalink
I'm afraid not.

I think you've got to debug it deeper to determine precisely what the
situation is that you're encountering.

I wonder if you're getting multiple concurrent threads occurring when
you up the rate - and you just don't have the memory to create
multiple concurrent images.

Dave
Jim Pike
2011-01-26 02:08:41 UTC
Permalink
Post by David Lowndes
I'm afraid not.
I think you've got to debug it deeper to determine precisely what the
situation is that you're encountering.
I wonder if you're getting multiple concurrent threads occurring when
you up the rate - and you just don't have the memory to create
multiple concurrent images.
Dave
Yeah the problem is I debugged down to get that line where the error
occured but debugging further it's hard to get any useful information.
David Lowndes
2011-01-26 08:23:07 UTC
Permalink
Post by Jim Pike
Yeah the problem is I debugged down to get that line where the error
occured but debugging further it's hard to get any useful information.
OK, so when that situation occurs, have you checked if other threads
have created a metafile?
What about available resources (GDI handles, memory)?

Dave
Jim Pike
2011-02-11 16:27:23 UTC
Permalink
Well after some WinDBG training I was able to get a deep trace on a
working call to CloseEnhMetaFile and a failing call (returning NULL).
They diverge here:

9 0 [ 3] GDI32!CreateICA
23 0 [ 4] GDI32!bCreateDCA
4 0 [ 5] ntdll!_stricmp
81 0 [ 5] ntdll!__ascii_stricmp eax = 0
40 85 [ 4] GDI32!bCreateDCA
38 0 [ 5] GDI32!hdcCreateDCW
3 0 [ 6] GDI32!NtGdiOpenDCW
2 0 [ 7] ntdll!KiFastSystemCall
1 0 [ 6] GDI32!NtGdiOpenDCW eax = 0
60 6 [ 5] GDI32!hdcCreateDCW eax =
0 <==== THIS LINE STARTS THE DIVERGE
55 151 [ 4] GDI32!bCreateDCA eax = 0
11 206 [ 3] GDI32!CreateICA eax = 0

That again leads me to believe GDI can no longer create the objects.
However, when I look at Task Manager or GDIView they both say w3wp.exe
has ZERO GDI objects. It's all confusing to me. How can they be zero?
That of course leads me to think I am not running out of handles.
David Lowndes
2011-02-15 09:30:22 UTC
Permalink
Post by Jim Pike
...
That again leads me to believe GDI can no longer create the objects.
However, when I look at Task Manager or GDIView they both say w3wp.exe
has ZERO GDI objects. It's all confusing to me. How can they be zero?
That of course leads me to think I am not running out of handles.
Hi Jim,

I'm afraid I'm out of suggestions. I'm not sure how you'll be able to
progress this further without some inspiration and possibly paid
support from MS.

Dave

Loading...