Closed
Bug 29358
Opened 25 years ago
Closed 24 years ago
font maps are not released on Windows
Categories
(Core :: Layout, defect, P3)
Tracking
()
VERIFIED
FIXED
mozilla0.9
People
(Reporter: rbs, Assigned: tetsuroy)
Details
(Keywords: memory-leak)
Attachments
(9 files)
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review | |
(deleted),
patch
|
Details | Diff | Splinter Review |
To keep track of glyphs that are available within a font, the font engine
allocates a 64K bit array for each font. But the problem is that these maps
are kept forever, and are not free'ed upon exiting from Mozilla.
Erik has explained that because these maps are expensive to create,
they are created once and cached as long as possible in a hash table.
And that he is planning to add a "shutdown listener" that will free
these maps just before shutdown.
So I am filling this bug for the record, and as reminder to him.
I think that there is also a plan to enable the sharing of identical
maps between fonts, so this is worth reminding here as well.
Erik, in order to support measurement/drawing based on an array of
glyph indices, rather than a standard string, I was forced to implement
the GetGlyphIndices() function. It is based on the GetGlyph() function that
was fortuitously in nsFontMetricsWin.cpp !!
[lxr.mozilla.org/seamonkey/source/gfx/src/windows/nsFontMetricsWin.cpp#244]
This GetGlyphIndices() function needs the CMAP itself. And so I have
been forced to cache this CMAP as well! This GetGlyphIndices() function
allow to fix a long-standing problem that have been affecting
GetBoundingMetrics(). Because GetGlyphOutlineW doesn't exist on Win95/Win98,
nsFontWinUnicode::GetBoundingMetrics() will systematically failing on some
inputs. This problem is documented in the MS' Knowledge Base Q241358:
"The GetGlyphOutlineW Function Fails on Windows 95 and Windows 98"
http://support.microsoft.com/support/kb/articles/Q241/3/58.ASP
With the GetGlyphIndices() function, it is now possible to use the work-around
suggested there, i.e., use GetGlyphOutlineA in conjuction with the
GGO_GLYPH_INDEX option. This, indeed, provides the desired metrics.
I renamed GetGlyph() as GetGlyphIndex() and added a couple of changes
to do the computations % 65536, as suggested in:
Q241020 HOWTO: Translate Unicode Character Codes to TrueType Glyph
http://support.microsoft.com/support/kb/articles/Q241/0/20.ASP
Again, the main issue that is in correlation with this bug is that the CMAP
itself has to be cached --for performance reasons. Currently, this is done
lazily. If nsFontWinUnicode::GetBoundingMetrics() is not called, the CMAP is
never kept --the first call causes the CMAP to be cached.
This only happens for nsFontWinUnicode fonts (not for other variants).
I know that you are particularly careful with nsFontWinUnicode fonts
since these are the most used fonts. That's why I am expounding on all these
details.
I will submit the code (which is still in #ifdef MOZ_MATHML) to your perusal
sometimes next week.
Updated•25 years ago
|
Status: NEW → ASSIGNED
Target Milestone: M17
Updated•24 years ago
|
Summary: font maps are not released → font maps are not released on Windows
Comment 3•24 years ago
|
||
reassign to yokoyama and mark it as M23.
We should figure out what we should do with this . This may (or may not) help
the embedding work.
Assignee: erik → yokoyama
Status: ASSIGNED → NEW
Target Milestone: M18 → M23
Assignee | ||
Updated•24 years ago
|
Status: NEW → ASSIGNED
Assignee | ||
Comment 4•24 years ago
|
||
Erik<-What is the best way to solve this problem? I would appreciate if you can
help me to jump-start on this issue.
Assignee | ||
Comment 5•24 years ago
|
||
Erik<- I have a patch for this bug; but I noticed the following:
1) when you close the ProfileManager and start the browser, the ProfileManager
calls to ~nsFontMetricsWin() via nsPresContext::PreferenceChanged().
Does this make sense? Does the FontMetrics Cache need to be flushed
by changing the Preference?
Comment 6•24 years ago
|
||
Yes, this makes sense.
Assignee | ||
Comment 7•24 years ago
|
||
Assignee | ||
Comment 8•24 years ago
|
||
erik<- can you review the patch? ~nsFontMetricsWin() gets called on destroy and
freeing the Cache. I did the pre-checkin tests as well.
Comment 9•24 years ago
|
||
Roy, in order to free both the hash table *and* its contents, you need to pass
the 5th argument to PR_NewHashTable(). See how it is done in
mozilla/modules/libpref/src/prefapi.c.
Also, gGlobalFonts does not own the maps, so you shouldn't free those. The maps
are owned by the gFontMaps hash table, so they should be freed when you free
that hash table.
Comment 10•24 years ago
|
||
has anyone used Purify to look at the problem and patch?
Comment 11•24 years ago
|
||
No, I haven't. Roy, do you have Purify?
Assignee | ||
Comment 12•24 years ago
|
||
No, I don't. I made the correction to my patch and would like to test the code.
Brian, if you have the purify, can I try it?
Assignee | ||
Comment 13•24 years ago
|
||
Assignee | ||
Comment 14•24 years ago
|
||
Erik: can you review the code for me to see if I am missing something else?
I (with Brian's help) am in the process of getting the Purify; but I want to
get your option. Thanks. :)
Assignee | ||
Comment 15•24 years ago
|
||
Updating the target milestone.
Target Milestone: --- → mozilla0.8
Comment 16•24 years ago
|
||
Hi Roy, please take another look at gFontMaps in nsFontMetricsWin.cpp. When
entries are added to the hash table, we first create an nsFontInfo object using
the "new" operator, so we should use "delete" to free it. Also, it contains a
pointer to the 8K array (the map), which should also be freed. This pointer
sometimes points to an object allocated using PR_Calloc(), so you need to use
PR_Free() to free those, but it sometimes points to "emptyMap", which we only
create once, so we should only delete it once (i.e. *not* in the hash table
free routine, but in a separate call in FreeGlobals()).
There may be other problems in your patch, but I'd like you to fix them before
I take another look.
Assignee | ||
Comment 17•24 years ago
|
||
Assignee | ||
Comment 18•24 years ago
|
||
Erik: Thanks for the quick look. I modify the nsFontInfo constructor to the
emptymap identifier making sure to free the emptymap only ONCE.
nsFontInfo(... , BOOL aEmptymap=FALSE)
Can you give a yet another quick look at the code so that I can check-in before
mozilla0.8 milestone? Thanks:)
Reporter | ||
Comment 19•24 years ago
|
||
There is also a lack of error-checkin in case the allocs failed. These may have
undesirable effects later, or at layers upper to GFX.
Also when mCMAP is cached, it should be freed as well. This happens in MathML.
So, the one-liner:
+ if (fontInfo)
+ {
+ if (!fontInfo->bEmptymap && fontInfo->mMap)
+ PR_Free(fontInfo->mMap);
#ifdef MOZ_MATHML
+ if (fontInfo->mCMAP.mData)
+ PR_Free(fontInfo->mCMAP.mData);
#endif
+ delete(fontInfo);
+}
Assignee | ||
Comment 20•24 years ago
|
||
rbs< Thank you for your input. I'll definitely include your request.
Comment 21•24 years ago
|
||
Hi Roy,
Adding a boolean member variable to nsFontInfo is not such a good idea because:
1. it increases the size of nsFontInfo
2. it is too much trouble to remember to pass the boolean to the constructor
(a future programmer could easily forget to do that)
Instead, you should just have some code that checks whether the map is the
empty map before deleting it. For example:
if (fontInfo->mMap && (fontInfo->mMap != gEmptyMap)) {
PR_Free(fontInfo->mMap);
}
Note the 'g' in front of "gEmptyMap". It doesn't need to be inside a class,
either.
Assignee | ||
Comment 22•24 years ago
|
||
Assignee | ||
Comment 23•24 years ago
|
||
Erik: Anything else you see I should do?
Comment 24•24 years ago
|
||
Hi Roy,
emptyMap should be called gEmptyMap, and it should be set to nsnull after
freeing it.
Objects of type nsFontWeightEntry are created using "new", so they must be
destroyed using "delete". mWeightTable is a PRUint16, so it should not be set to
nsnull, which is reserved for pointers. Also, it is not necessary to set a
PRUint16 to zero in a destructor.
The keys and values in gFamilyNames are created using "new", so they should be
destroyed using "delete".
Finally, as rbs already mentioned, all of the allocations should be checked for
failure. For example, gFontMaps is not checked after creation.
Assignee | ||
Comment 25•24 years ago
|
||
Assignee | ||
Comment 26•24 years ago
|
||
Erik: can you review it for me?
Comment 27•24 years ago
|
||
The keys in gFontMaps are created using "new", so they must be destroyed using
"delete" after casting to nsString*.
If the allocation for gEmptyMap fails, you need to destroy gFontMaps before
returning nsnull. You would also need to set gInitializedFontMaps to zero.
However, I believe it is cleaner to take the approach that we took in
mozilla/gfx/src/gtk/nsFontMetricsGTK.cpp. There, we initialize all the globals
in nsFontMetricsGTK::Init(), by calling InitGlobals(). This way, we can use a
single variable instead of 3 separate ones (gInitializedFontMaps,
gInitializedFamilyNames and gInitializedFontWeights).
Sorry about all these comments. My, my isn't memory management in C++ fun!
Assignee | ||
Updated•24 years ago
|
Target Milestone: mozilla0.8 → mozilla0.9
Assignee | ||
Comment 28•24 years ago
|
||
Comment 29•24 years ago
|
||
The keys in gFontMaps are of type nsString*. In order to call nsString's
destructor, you need to cast the pointer to nsString*, and then pass the result
to delete. For example:
delete (nsString*) (he->key);
nsFontInfo's mMap is allocated using PR_Calloc, so it must be freed using
PR_Free.
You need to set gInitializedFontMaps to zero if gFontMaps allocation fails.
Alternatively, you could set gInitializedFontMaps to 1 after gFontMaps and
gEmptyMap allocation succeeds (or take the approach I took in
nsFontMetricsGTK).
When the allocation for nsFontInfo fails, you need to delete name and
PR_Free(map) if map != gEmptyMap.
Also, PL_HashTableAdd itself can fail (returning 0), so you need error checking
there too.
The keys in gFontWeights are of type nsFontWeightEntry*, so you need to cast to
that type, and then call delete. There is also a variable called
fontWeightEntry that is set but not used.
You need to set gInitializedFontWeights to zero if the allocation for
gFontWeights fails.
The keys and values in gFamilyNames are of type nsString*, so you need to cast
to that type, and then call delete.
You need to delete one or both of name and winName when either or both of their
allocations fail(s).
Why did you add code for mSubsets? The added code looks wrong.
Reporter | ||
Comment 30•24 years ago
|
||
Since most (if not all) of these variables are needed before the code can run,
having a single gInitialized flag and folding all the alloc/delete in a pair of
InitGlobals()/FreeGlobals() like Erik did in nsFontMetricsGTK, things will be
much easier to maintain and nicer for you Roy. I also filled bug 45737 where I
alluded to such things. So if you bite the bullet on this one, that may help
also in view of that bug.
Assignee | ||
Comment 31•24 years ago
|
||
Is there any particular reasons why we use PR_Calloc and new separately? Can
we keep it consistant thru out the app/module? We have gFontMaps,
gFamilyNames, gFontWeights, gGlobalFonts, gEmptyFont and others with different
memory allocation methods.
Comment 32•24 years ago
|
||
There is no good reason to use "new" in some cases and PR_Calloc in others. If
you want, I think it would be OK to change the PR_*alloc calls to new, but
please be careful, because calloc and PR_Calloc write zeroes in the new memory
after allocating it, while "new" doesn't (I think).
Assignee | ||
Comment 33•24 years ago
|
||
Reporter | ||
Comment 34•24 years ago
|
||
info = new nsFontInfo(NS_FONT_TYPE_NON_UNICODE, DEFAULT_CHARSET,
map);
if (info) {
// XXX check to see if an identical map has already been added
- PL_HashTableAdd(gFontMaps, name, info);
+ if (PL_HashTableAdd(gFontMaps, name, info))
+ return map;
+ else { // error adding entry to the table
+ delete info;
+ delete name;
+ if (map != gEmptyMap)
+ PR_Free(map);
+ return nsnull;
+ }
}
- return map;
+ else { // error allocating FontInfo
+ delete name;
+ if (map != gEmptyMap)
+ PR_Free(map);
+ return nsnull;
+ }
}
becomes in a much compact way:
info = new nsFontInfo(NS_FONT_TYPE_NON_UNICODE, DEFAULT_CHARSET, map);
if (info) {
// XXX check to see if an identical map has already been added
PL_HashTableAdd(gFontMaps, name, info);
if (PL_HashTableAdd(gFontMaps, name, info))
return map;
}
}
// if we reach here, no map was added to the hashtable
if (info) delete info;
// error adding entry to the table
delete name;
if (map != gEmptyMap) PR_Free(map);
return nsnull;
[Same pattern elsewhere ...], for exemple at:
+ // Store it in font weight HashTable.
+ nsFontWeightEntry* fontWeightEntry = new nsFontWeightEntry;
+ if (fontWeightEntry) {
+ fontWeightEntry->mFontName = low;
+ fontWeightEntry->mWeightTable = weightTable;
+ PL_HashTableAdd(gFontWeights, fontWeightEntry, fontWeightEntry);
+ }
+ else { // error checking
+ return 0;
+ }
becomes ... (note: you didn't check that the HT-add was ok]
+ nsFontWeightEntry* fontWeightEntry = new nsFontWeightEntry;
+ if (fontWeightEntry) {
+ fontWeightEntry->mFontName = low;
+ fontWeightEntry->mWeightTable = weightTable;
if (PL_HashTableAdd(gFontWeights, fontWeightEntry, fontWeightEntry)) {
return fontWeightEntry;
}
}
// if we reach here, no fontWeightEntry was added to the hashtable
if (fontWeightEntry) delete fontWeightEntry;
return 0;
[Same pattern there and elsewhere ...]
+ if (PL_HashTableAdd(gFamilyNames, name, (void*) winName) == 0) {
becomes
+ if (PL_HashTableAdd(gFamilyNames, name, (void*) winName)) {
...
r=rbs, assuming you get the suggested compact patterns.
Comment 35•24 years ago
|
||
+PR_STATIC_CALLBACK(void*) fontmap_AllocTable(void *pool, size_t size)
+{
+ return PR_MALLOC(size);
+}
+
+PR_STATIC_CALLBACK(void) fontmap_FreeTable(void *pool, void *item)
+{
+ PR_Free(item);
+}
Nit: why PR_MALLOC but PR_Free? PR_Malloc is the matching function, PR_MALLOC
is just a macro that calls PR_Malloc.
More significant point: why not fuse the allocation of nsFontInfo with the
allocation of its PLHashEntry?
+PR_STATIC_CALLBACK(void) fontmap_FreeEntry(void *pool, PLHashEntry *he,
PRUint32 flag)
+{
+ nsFontInfo *fontInfo = (nsFontInfo *) he->value;
+ if (fontInfo)
+ {
+ if (fontInfo->mMap && (fontInfo->mMap != nsFontMetricsWin::gEmptyMap))
+ PR_Free(fontInfo->mMap);
+#ifdef MOZ_MATHML
+ if (fontInfo->mCMAP.mData)
+ PR_Free(fontInfo->mCMAP.mData);
+#endif
+ delete(fontInfo);
+ }
+
+ if (flag == HT_FREE_ENTRY) {
+ delete (nsString *) (he->key);
+ PR_Free(he);
+ }
+}
+
shows separate allocations for key, value, and entry, but since you are using
PLHashAllocOps, you can easily combine two or possibly all three of those into
one allocation, cutting down on malloc per-allocation overhead, cycles spent in
malloc and free, and heap fragmentation.
Instead of this:
info = new nsFontInfo(NS_FONT_TYPE_NON_UNICODE, DEFAULT_CHARSET, map);
if (info) {
// XXX check to see if an identical map has already been added
- PL_HashTableAdd(gFontMaps, name, info);
+ if (PL_HashTableAdd(gFontMaps, name, info))
+ return map;
+ else { // error adding entry to the table
+ delete info;
+ delete name;
+ if (map != gEmptyMap)
+ PR_Free(map);
+ return nsnull;
+ }
}
- return map;
+ else { // error allocating FontInfo
+ delete name;
+ if (map != gEmptyMap)
+ PR_Free(map);
+ return nsnull;
+ }
}
[Note also that there is an else after return above, which is unnecessary and a
bit confusing -- the return means *none* of the subsequent code in this method
executes if the return is taken, yet only some of that subsequent code is braced
in the else after the return.]
how about this:
PLHashEntry **hep, *he;
PLHashNumber hash = HashKey(name);
hep = PL_HashTableRawLookup(gFontMaps, hash, name);
he = *hep;
if (he) {
// XXX an identical map has already been added???
} else {
info = new nsFontInfo(NS_FONT_TYPE_NON_UNICODE, DEFAULT_CHARSET, map);
if (info) {
he = info;
if (PL_HashTableRawAdd(gFontMaps, hep, he, name, info))
return map;
// error adding entry to the table
delete info;
// fall through into "error allocating FontInfo" code
}
// error allocating FontInfo
delete name;
if (map != gEmptyMap)
PR_Free(map);
return nsnull;
}
and make class nsFontInfo : public PLHashEntry, of course? rbs's comment
suggest almost the same thing, but I think I've eliminated all redundant tests
of info in the cleanup code after the early 'return map;'.
Ask me anything if this doesn't make sense.
/be
Comment 36•24 years ago
|
||
Argh, I blew it in my attempt to type new code into this tiny textarea! Several
errors in my last comment, both in not showing the revised functions needed for
PLHashAllocOps that fuse nsFontInfo and PLHashEntry, and in the code I sketched
that adds a new entry.
Trying again, and using new/delete rather than PR_NEW/delete:
PR_STATIC_CALLBACK(PLHashEntry*) fontmap_AllocEntry(void *pool, const void *key)
{
return new nsFontInfo(NS_FONT_TYPE_NON_UNICODE, DEFAULT_CHARSET, nsnull);
}
PR_STATIC_CALLBACK(void) fontmap_FreeEntry(void *pool, PLHashEntry *he, PRUint32
flag)
{
nsFontInfo *fontInfo = NS_STATIC_CAST(nsFontInfo *, he);
if (fontInfo->mMap && (fontInfo->mMap != nsFontMetricsWin::gEmptyMap))
PR_Free(fontInfo->mMap);
#ifdef MOZ_MATHML
if (fontInfo->mCMAP.mData)
PR_Free(fontInfo->mCMAP.mData);
#endif
if (flag == HT_FREE_ENTRY) {
delete (nsString *) (he->key);
delete fontInfo;
}
}
Then the correct (revised from my last comment) code to add an entry, with some
pseudo-code in the XXX case, is:
PLHashEntry **hep, *he;
PLHashNumber hash = HashKey(name);
hep = PL_HashTableRawLookup(gFontMaps, hash, name);
he = *hep;
if (he) {
// XXX an identical map has already been added
info = he;
reinitialize info->mMap, info->mCMAP, etc.;
return map;
}
he = PL_HashTableRawAdd(gFontMaps, hep, hash, name, nsnull);
if (he) {
info = NS_STATIC_CAST(nsFontInfo*, he);
he->value = info; // so PL_HashTableLookup returns an nsFontInfo*
info->mMap = map;
return map;
}
delete name;
if (map != gEmptyMap)
PR_Free(map);
return nsnull;
Notice how the entry, which is the superclass of the fontinfo class, does not
get deleted and recreated if there is a duplication. Could that case arise?
Sorry for the confusion; hope I have the above right.
/be
Reporter | ||
Comment 37•24 years ago
|
||
That's a much compact and elegant way.
I recollect the intention is that if there is an identical map, then a no-op
follows. i.e., if the name of the font is the same (but some other attributes
are different e.g., 12pt vs. 24pt), then they don't matter, there is a total
re-use of the info stored in the hash because it doesn't depend on such
attributes:
if (he) {
// an identical map has already been added
info = he;
init other OUT params of the function: *aFontType, *aCharset
return info->map;
}
with the corresponding update:
PR_STATIC_CALLBACK(void) fontmap_FreeEntry(void *pool, PLHashEntry *he, PRUint32
flag)
{
if (flag == HT_FREE_ENTRY) {
nsFontInfo *fontInfo = NS_STATIC_CAST(nsFontInfo *, he);
if (fontInfo->mMap && (fontInfo->mMap != nsFontMetricsWin::gEmptyMap))
PR_Free(fontInfo->mMap);
#ifdef MOZ_MATHML
if (fontInfo->mCMAP.mData)
PR_Free(fontInfo->mCMAP.mData);
#endif
delete (nsString *) (he->key);
delete fontInfo;
}
}
Comment 38•24 years ago
|
||
rbs: I'll buy that. Thanks for the out param pseudo-code fix!
/be
Comment 39•24 years ago
|
||
Roger and Brendan, thanks for reviewing (IBM's bidi submission has been keeping
me very busy), and Roy, thanks for taking this bug. Had a quick look at the
most recent comments, and they look good.
Assignee | ||
Comment 40•24 years ago
|
||
Comment 41•24 years ago
|
||
Looks pretty good. There are a few else after return and else after break
non-sequiturs visible in the patch, you might find that fixing those makes the
code clearer, generally by making less exceptional cases (formerly in the
unnecessary else clauses) less indented.
Also, why not make gFontWeights fuse its value (which is also its key) with its
PLHashEntry, since you're already specializing fontweight_HashAllocOps? Just
new the nsFontWeightEntry in ..._allocEntry, and delete it in _freeEntry if
HT_FREE_ENTRY. Use PL_HashTableRawLookup and PL_HashTableRawAdd similarly to
the way they're used for gFontMaps.
/be
Assignee | ||
Comment 42•24 years ago
|
||
Assignee | ||
Comment 43•24 years ago
|
||
brendan: I look at the code for exceptional cases and they seem reasonable. I
wonder if the patch is acceptable for check-in.
Comment 44•24 years ago
|
||
Looks good, thanks. sr=brendan@mozilla.org.
/be
Reporter | ||
Comment 45•24 years ago
|
||
r=rbs
Assignee | ||
Comment 46•24 years ago
|
||
checked-in
Status: ASSIGNED → RESOLVED
Closed: 24 years ago
Resolution: --- → FIXED
You need to log in
before you can comment on or make changes to this bug.
Description
•