Project: chromium Issues People Development process History Sign in
New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.
Issue 121333 dynamic favicon causes memory leak on mac
Starred by 18 users Reported by rom...@gmail.com, Apr 1 2012 Back to list
Status: Untriaged
Owner: ----
Cc:
EstimatedDays: ----
NextAction: ----
OS: Mac
Pri: 2
Type: Bug



Sign in to add a comment
Chrome Version       : 19.0.1084.1
OS Version           : Linux 3.0.0-17-generic Ubuntu 11.10 x86_64
URLs (if applicable) : http://www.brillout.com/test/icon , http://www.brillout.com/test/icon-alt , http://www.timer-tab.com

What steps will reproduce the problem?
1. Go to one of the URLs
2. Wait a while
3. Observe how CPU usage increase over time
4. After waiting enough time: reaches 100% of CPU usage

What is the expected result?
1. Less CPU usage

What happens instead?
1. too much CPU usage

Note that commenting the line that sets the favicon HREF makes the CPU usage stay at 0%. Therefore the reason of the high CPU usage is caused by changing the favicon and not by generating the canvas.
The consequences of this unexpected behavior are:
-breaks websites that make substantial use of dynamic favicons like http://www.timer-tab.com. Timer Tab has over 100k Chrome Webstore users (I'm the dev of Timer Tab)
-makes websites using dynamic favicon like Gmail with the "Unread message icon" setting enabled use too much CPU (not tested)

Also Note that the second URL makes the favicon change differently: it removes and appends another LINK element to the HEAD element while the first URL changes the HREF attribute of the LINK element. 
Note that the attached files correspond to the first two URLs.

UserAgentString: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.1 Safari/536.5
 
icon-alt.html
1.4 KB View Download
icon.html
1.4 KB View Download
Comment 1 Deleted
Comment 2 by rom...@gmail.com, Apr 30 2012
I'm the dev of http://www.timer-tab.com (the URL provided with this issue).
Timer Tab stoped using dynamic favicons because of this bug.
Therefore to repreduce this bug please use the URLs http://www.brillout.com/test/icon , http://www.brillout.com/test/icon-alt
Comment 3 by rom...@gmail.com, May 24 2012
the CPU usage doesn't increase on chrome on my 32 bit Ubuntu. It does increase on my 64 bit Ubuntu though. So I suspect the Issue to only occur in the 64 bit implementation.
Comment 4 by rom...@gmail.com, May 27 2012
rectification: it did increase on my 32 bit Ubuntu. It just took longer. It took 2 hours to go up to 10% of CPU usage. Note that my 32 bit Ubuntu machine has a better CPU than my 64 bit Ubuntu machine
Project Member Comment 5 by bugdroid1@chromium.org, Mar 10 2013
Labels: -Area-Undefined
Comment 6 by Deleted ...@, Jan 30 2014
was this issue fixed?
Labels: Hotlist-ExcessiveCPU
Labels: Hotlist-Slow
Labels: -OS-Linux -Hotlist-ExcessiveCPU OS-Mac Performance-Memory
Status: Available
Summary: dynamic favicon causes memory leak on mac (was: dynamic favicon needs extremely high CPU usage: up to 100% of the CPU)
I've tested this out on Mac and Linux. I see no excessive CPU usage.

What I do see, however, is a memory leak on Mac. It looks like we're never freeing up the memory being allocated to the new favicon and it's growing unbounded. This does not happen on Windows.

Using the timer tab, my timer is at 6:40 and my memory usage is 101MB. I'll update in a half hour or so.
12:10 and 127MB
36:42 and 149MB
Owner: asvitk...@chromium.org
Status: Assigned
I'll take a look.
2:32:42 and 284MB.

ok I'll stop now :)
After running for a bit, these are the alloc with the largest alloc counts:

2240 calls for 376320 bytes: thread_138567000 |thread_start
_pthread_struct_init
_pthread_body
base::(anonymous namespace)::ThreadFunc(void*)
base::SimpleThread::ThreadMain()
base::DelegateSimpleThread::Run()
non-virtual thunk to cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::TaskGraphRunner::Run()
cc::TaskGraphRunner::RunTaskWithLockAcquired()
cc::(anonymous namespace)::RasterTaskImpl::RunOnWorkerThread()
cc::(anonymous namespace)::RasterTaskImpl::Raster(cc::RasterSource const*)
cc::(anonymous namespace)::RasterBufferImpl::Playback(cc::RasterSource const*, gfx::Rect const&, float)
cc::OneCopyTileTaskWorkerPool::PlaybackAndScheduleCopyOnWorkerThread(scoped_ptr<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer, base::DefaultDeleter<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer> >, scoped_ptr<cc::ScopedResource, base::DefaultDeleter<cc::ScopedResource> >, cc::Resource const*, cc::RasterSource const*, gfx::Rect const&, float)
cc::TileTaskWorkerPool::PlaybackToMemory(void*, cc::ResourceFormat, gfx::Size const&, int, cc::RasterSource const*, gfx::Rect const&, float)
cc::PicturePileImpl::PlaybackToCanvas(SkCanvas*, gfx::Rect const&, float) const
cc::PicturePileImpl::RasterCommon(SkCanvas*, SkDrawPictureCallback*, gfx::Rect const&, float, bool) const
cc::Picture::Raster(SkCanvas*, SkDrawPictureCallback*, cc::Region const&, float) const
SkCanvas::drawPicture(SkPicture const*)
SkCanvas::onDrawPicture(SkPicture const*, SkMatrix const*, SkPaint const*)
SkPicture::playback(SkCanvas*, SkPicture::AbortCallback*) const
SkRecordDraw(SkRecord const&, SkCanvas*, SkPicture const* const*, SkCanvasDrawable* const*, int, SkBBoxHierarchy const*, SkPicture::AbortCallback*)
void SkRecord::visit<void, SkRecords::Draw>(unsigned int, SkRecords::Draw&) const
void SkRecord::Record::visit<void, SkRecords::Draw>(SkRecord::Type8, SkRecords::Draw&) const
void SkRecords::Draw::operator()<SkRecords::Restore>(SkRecords::Restore const&)
void SkRecords::Draw::draw<SkRecords::Restore>(SkRecords::Restore const&)
SkCanvas::restore()
SkCanvas::internalRestore()
SkCanvas::internalDrawDevice(SkBaseDevice*, int, int, SkPaint const*)
SkImageFilter::filterImage(SkImageFilter::Proxy*, SkBitmap const&, SkImageFilter::Context const&, SkBitmap*, SkIPoint*) const
(anonymous namespace)::CacheImpl::set(SkImageFilter::Cache::Key const&, SkBitmap const&, SkIPoint const&)
operator new(unsigned long)
malloc
malloc_zone_malloc 

2213 calls for 371784 bytes: thread_138d6a000 |thread_start
_pthread_struct_init
_pthread_body
base::(anonymous namespace)::ThreadFunc(void*)
base::SimpleThread::ThreadMain()
base::DelegateSimpleThread::Run()
non-virtual thunk to cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::TaskGraphRunner::Run()
cc::TaskGraphRunner::RunTaskWithLockAcquired()
cc::(anonymous namespace)::RasterTaskImpl::RunOnWorkerThread()
cc::(anonymous namespace)::RasterTaskImpl::Raster(cc::RasterSource const*)
cc::(anonymous namespace)::RasterBufferImpl::Playback(cc::RasterSource const*, gfx::Rect const&, float)
cc::OneCopyTileTaskWorkerPool::PlaybackAndScheduleCopyOnWorkerThread(scoped_ptr<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer, base::DefaultDeleter<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer> >, scoped_ptr<cc::ScopedResource, base::DefaultDeleter<cc::ScopedResource> >, cc::Resource const*, cc::RasterSource const*, gfx::Rect const&, float)
cc::TileTaskWorkerPool::PlaybackToMemory(void*, cc::ResourceFormat, gfx::Size const&, int, cc::RasterSource const*, gfx::Rect const&, float)
cc::PicturePileImpl::PlaybackToCanvas(SkCanvas*, gfx::Rect const&, float) const
cc::PicturePileImpl::RasterCommon(SkCanvas*, SkDrawPictureCallback*, gfx::Rect const&, float, bool) const
cc::Picture::Raster(SkCanvas*, SkDrawPictureCallback*, cc::Region const&, float) const
SkCanvas::drawPicture(SkPicture const*)
SkCanvas::onDrawPicture(SkPicture const*, SkMatrix const*, SkPaint const*)
SkPicture::playback(SkCanvas*, SkPicture::AbortCallback*) const
SkRecordDraw(SkRecord const&, SkCanvas*, SkPicture const* const*, SkCanvasDrawable* const*, int, SkBBoxHierarchy const*, SkPicture::AbortCallback*)
void SkRecord::visit<void, SkRecords::Draw>(unsigned int, SkRecords::Draw&) const
void SkRecord::Record::visit<void, SkRecords::Draw>(SkRecord::Type8, SkRecords::Draw&) const
void SkRecords::Draw::operator()<SkRecords::Restore>(SkRecords::Restore const&)
void SkRecords::Draw::draw<SkRecords::Restore>(SkRecords::Restore const&)
SkCanvas::restore()
SkCanvas::internalRestore()
SkCanvas::internalDrawDevice(SkBaseDevice*, int, int, SkPaint const*)
SkImageFilter::filterImage(SkImageFilter::Proxy*, SkBitmap const&, SkImageFilter::Context const&, SkBitmap*, SkIPoint*) const
(anonymous namespace)::CacheImpl::set(SkImageFilter::Cache::Key const&, SkBitmap const&, SkIPoint const&)
operator new(unsigned long)
malloc
malloc_zone_malloc 

2041 calls for 342888 bytes: thread_138567000 |thread_start
_pthread_struct_init
_pthread_body
base::(anonymous namespace)::ThreadFunc(void*)
base::SimpleThread::ThreadMain()
base::DelegateSimpleThread::Run()
non-virtual thunk to cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::TaskGraphRunner::Run()
cc::TaskGraphRunner::RunTaskWithLockAcquired()
cc::(anonymous namespace)::RasterTaskImpl::RunOnWorkerThread()
cc::(anonymous namespace)::RasterTaskImpl::Raster(cc::RasterSource const*)
cc::(anonymous namespace)::RasterBufferImpl::Playback(cc::RasterSource const*, gfx::Rect const&, float)
cc::OneCopyTileTaskWorkerPool::PlaybackAndScheduleCopyOnWorkerThread(scoped_ptr<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer, base::DefaultDeleter<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer> >, scoped_ptr<cc::ScopedResource, base::DefaultDeleter<cc::ScopedResource> >, cc::Resource const*, cc::RasterSource const*, gfx::Rect const&, float)
cc::TileTaskWorkerPool::PlaybackToMemory(void*, cc::ResourceFormat, gfx::Size const&, int, cc::RasterSource const*, gfx::Rect const&, float)
cc::PicturePileImpl::PlaybackToCanvas(SkCanvas*, gfx::Rect const&, float) const
cc::PicturePileImpl::RasterCommon(SkCanvas*, SkDrawPictureCallback*, gfx::Rect const&, float, bool) const
cc::Picture::Raster(SkCanvas*, SkDrawPictureCallback*, cc::Region const&, float) const
SkCanvas::drawPicture(SkPicture const*)
SkCanvas::onDrawPicture(SkPicture const*, SkMatrix const*, SkPaint const*)
SkPicture::playback(SkCanvas*, SkPicture::AbortCallback*) const
SkRecordDraw(SkRecord const&, SkCanvas*, SkPicture const* const*, SkCanvasDrawable* const*, int, SkBBoxHierarchy const*, SkPicture::AbortCallback*)
void SkRecord::visit<void, SkRecords::Draw>(unsigned int, SkRecords::Draw&) const
void SkRecord::Record::visit<void, SkRecords::Draw>(SkRecord::Type8, SkRecords::Draw&) const
void SkRecords::Draw::operator()<SkRecords::Restore>(SkRecords::Restore const&)
void SkRecords::Draw::draw<SkRecords::Restore>(SkRecords::Restore const&)
SkCanvas::restore()
SkCanvas::internalRestore()
SkCanvas::internalDrawDevice(SkBaseDevice*, int, int, SkPaint const*)
SkImageFilter::filterImage(SkImageFilter::Proxy*, SkBitmap const&, SkImageFilter::Context const&, SkBitmap*, SkIPoint*) const
SkColorFilterImageFilter::onFilterImage(SkImageFilter::Proxy*, SkBitmap const&, SkImageFilter::Context const&, SkBitmap*, SkIPoint*) const
SkDeviceImageFilterProxy::createDevice(int, int)
SkBitmapDevice::onCreateCompatibleDevice(SkBaseDevice::CreateInfo const&)
SkBitmapDevice::Create(SkImageInfo const&, SkDeviceProperties const*)
SkBitmap::tryAllocPixels(SkImageInfo const&)
SkBitmap::tryAllocPixels(SkImageInfo const&, unsigned long)
SkMallocPixelRef::PRFactory::create(SkImageInfo const&, unsigned long, SkColorTable*)
SkMallocPixelRef::NewAllocate(SkImageInfo const&, unsigned long, SkColorTable*)
operator new(unsigned long)
malloc
malloc_zone_malloc 

2013 calls for 338184 bytes: thread_138d6a000 |thread_start
_pthread_struct_init
_pthread_body
base::(anonymous namespace)::ThreadFunc(void*)
base::SimpleThread::ThreadMain()
base::DelegateSimpleThread::Run()
non-virtual thunk to cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::(anonymous namespace)::TileTaskGraphRunner::Run()
cc::TaskGraphRunner::Run()
cc::TaskGraphRunner::RunTaskWithLockAcquired()
cc::(anonymous namespace)::RasterTaskImpl::RunOnWorkerThread()
cc::(anonymous namespace)::RasterTaskImpl::Raster(cc::RasterSource const*)
cc::(anonymous namespace)::RasterBufferImpl::Playback(cc::RasterSource const*, gfx::Rect const&, float)
cc::OneCopyTileTaskWorkerPool::PlaybackAndScheduleCopyOnWorkerThread(scoped_ptr<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer, base::DefaultDeleter<cc::ResourceProvider::ScopedWriteLockGpuMemoryBuffer> >, scoped_ptr<cc::ScopedResource, base::DefaultDeleter<cc::ScopedResource> >, cc::Resource const*, cc::RasterSource const*, gfx::Rect const&, float)
cc::TileTaskWorkerPool::PlaybackToMemory(void*, cc::ResourceFormat, gfx::Size const&, int, cc::RasterSource const*, gfx::Rect const&, float)
cc::PicturePileImpl::PlaybackToCanvas(SkCanvas*, gfx::Rect const&, float) const
cc::PicturePileImpl::RasterCommon(SkCanvas*, SkDrawPictureCallback*, gfx::Rect const&, float, bool) const
cc::Picture::Raster(SkCanvas*, SkDrawPictureCallback*, cc::Region const&, float) const
SkCanvas::drawPicture(SkPicture const*)
SkCanvas::onDrawPicture(SkPicture const*, SkMatrix const*, SkPaint const*)
SkPicture::playback(SkCanvas*, SkPicture::AbortCallback*) const
SkRecordDraw(SkRecord const&, SkCanvas*, SkPicture const* const*, SkCanvasDrawable* const*, int, SkBBoxHierarchy const*, SkPicture::AbortCallback*)
void SkRecord::visit<void, SkRecords::Draw>(unsigned int, SkRecords::Draw&) const
void SkRecord::Record::visit<void, SkRecords::Draw>(SkRecord::Type8, SkRecords::Draw&) const
void SkRecords::Draw::operator()<SkRecords::Restore>(SkRecords::Restore const&)
void SkRecords::Draw::draw<SkRecords::Restore>(SkRecords::Restore const&)
SkCanvas::restore()
SkCanvas::internalRestore()
SkCanvas::internalDrawDevice(SkBaseDevice*, int, int, SkPaint const*)
SkImageFilter::filterImage(SkImageFilter::Proxy*, SkBitmap const&, SkImageFilter::Context const&, SkBitmap*, SkIPoint*) const
SkColorFilterImageFilter::onFilterImage(SkImageFilter::Proxy*, SkBitmap const&, SkImageFilter::Context const&, SkBitmap*, SkIPoint*) const
SkDeviceImageFilterProxy::createDevice(int, int)
SkBitmapDevice::onCreateCompatibleDevice(SkBaseDevice::CreateInfo const&)
SkBitmapDevice::Create(SkImageInfo const&, SkDeviceProperties const*)
SkBitmap::tryAllocPixels(SkImageInfo const&)
SkBitmap::tryAllocPixels(SkImageInfo const&, unsigned long)
SkMallocPixelRef::PRFactory::create(SkImageInfo const&, unsigned long, SkColorTable*)
SkMallocPixelRef::NewAllocate(SkImageInfo const&, unsigned long, SkColorTable*)
operator new(unsigned long)
malloc
malloc_zone_malloc 

Cc: hajimehoshi@chromium.org dmikurube@chromium.org kouhei@chromium.org
(The above was recorded by running Chrome with MallocStackLogging=1 and running malloc_history -allByCount command on a Mac.)

However, the above only account for about 400kb each, which doesn't explain the growth of dozens of megabytes. I also tried Chrome dev's tools JS heap dumps and didn't see any growth there.

My suspicion is the leaks are in memory that's not on the v8 heap and are also not allocated using system malloc - i.e. maybe using partition alloc or something else in Blink with which I'm not familiar with.

cc'ing some folks who've worked on memory analysis in Chrome to see if they have better ideas on how to diagnose this.


Cc: japhet@chromium.org
Adding Nate because he's the only person I know who's gotten close to favicons recently :)
Cc: asvitk...@chromium.org
Labels: -Hotlist-Slow
Owner: ----
Status: Available
Removing Hotlist-Slow since there doesn't appear to be a performance problem, only a leak now.

Also, marking available since I've ran into a dead end with my investigations into the memory leak. Hoping someone who knows more about Blink's memory on the cc list could lend a hand with the investigation.
Project Member Comment 18 by sheriffbot@chromium.org, Jun 21 2016
Labels: Hotlist-Recharge-Cold
Status: Untriaged
This issue has been available for more than 365 days, and should be re-evaluated. Hotlist-Recharge-Cold label is added for tracking. Please re-triage this issue.

For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
Maybe oilpan has effectively fixed this. I increased the rate of favicon creation and sampled with `MallocStackLogging=YES and `malloc_history <renderer_pid> -allBySize`. The memory for me grew to ~180MB but then hovered around there for a long time. I don't think the growth is unbounded any longer.

Most of the memory activity seems to come from

ImageData* HTMLCanvasElement::toImageData(SourceDrawingBuffer sourceBuffer, SnapshotReason reason) const

It returns a raw-looking pointer, but ImageData* is some garbage collected thingo.

The "leak" counts do increase for a while - maybe oilpan isn't doing as good a job as it can. Or maybe malloc_history isn't good at interpreting GC stuff.

One persistent culprit is "851 calls for 435712 bytes" in the FontCache. I verified FontCache::createFontPlatformData is called only once though, so these are probably just individual glyphs or something just sitting in the cache.

for toImageData:
 - After ~3 minutes:
  2163 calls for 9551808 bytes
  2163 calls for 397992 bytes
  2163 calls for 363384 bytes
  2163 calls for 294168 bytes
  2163 calls for 224952 bytes
  2163 calls for 155736 bytes

--> 10MB


 - After ~5 minutes:
  2631 calls for 11618496 bytes
  2631 calls for 484104 bytes
  2631 calls for 442008 bytes
  2631 calls for 357816 bytes
  2631 calls for 273624 bytes
  2631 calls for 189432 bytes

--> 13MB


 - After ~30 minutes
  425 calls for 1876800 bytes
  425 calls for 78200 bytes
  425 calls for 71400 bytes
  425 calls for 57800 bytes
..
icon-alt.html
1.4 KB View Download
favicon_leak5a.txt
88.7 KB View Download
Sign in to add a comment