// GC.cpp: Garbage Collector for Gnash // // Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "GC.h" #include #include namespace gnash { // Only initialize the GC once. static boost::once_flag once = BOOST_ONCE_INIT; // Track additions to the list since it was pushed to the GC // here. static boost::thread_specific_ptr GcPendingList; static boost::thread_specific_ptr GcManageInvoke; // Only track our depth if we are debugging. #ifndef NDEBUG #ifdef GCDEBUG static boost::thread_specific_ptr GcInvokeDepth; bool GcInvoke::isOn() { return true; if (GcInvokeDepth.get() == NULL) { GcInvokeDepth.reset(new int(0)); return false; } return *GcInvokeDepth > 0; } #endif /* GCDEBUG */ #endif /* !NDEBUG */ void GcInvoke::enter(bool manage, bool *store_old, GcResourceList::iterator *iter) { if (GcManageInvoke.get() == NULL) { GcManageInvoke.reset(new bool(manage)); *store_old = false; } else { *store_old = *GcManageInvoke; *GcManageInvoke = manage; } #ifndef NDEBUG #ifdef GCDEBUG if (GcInvokeDepth.get() == NULL) { GcInvokeDepth.reset(new int(0)); } ++(*GcInvokeDepth); #endif /* GCDEBUG */ #endif /* !NDEBUG */ if (GcPendingList.get() == NULL) { GcPendingList.reset(new GcResourceList); } *iter = GcPendingList->begin(); } void GcInvoke::leave(GcInvoke *pInvoke) { // Our usual checks aren't necessary, since we did them in enter. (*GcManageInvoke) = pInvoke->m_manage; #ifndef NDEBUG #ifdef GCDEBUG --(*GcInvokeDepth); #endif /* GCDEBUG */ #endif /* !NDEBUG */ // Add the managed stuff to the Gc, but only if we are depthless. // Don't erase -- the GC will do that by using splice() if (pInvoke->m_iter == GcPendingList->end()) { GC::manage(GcPendingList.get(), pInvoke->m_iter); } } // This is a function object whose sole job is to make sure that the // garbage collector runs. class GC_runner { public: GC_runner(GC* g) : mGC(g) { } void operator()() { while (1) { if (mGC->m_shutdown) return; if (!mGC->m_collect) boost::thread::yield(); else mGC->collect(); } } static boost::thread* begin(GC* g) { GC_runner runit(g); boost::thread *f = new boost::thread(runit); return f; } private: GC* mGC; }; // Define the static data members declared in the definition of GC. // If we have 10 new resources, trigger an automatic collection. // Try different values of this to opitmize the GC. const unsigned int GC::mAutoTriggerSize = 10; // Although the externally useful functions are static, we operate on an // actual object. GC* GC::m_TheGC = NULL; GC* GC::getGC() { boost::call_once(&GC::initialize, once); return m_TheGC; } // Constructor of the GC. // This is called once in the whole program. Please don't inline it. GC::GC() : m_new_additions(0), m_res_list_add(), m_res_list_mark(), m_root_list(), m_res_mutex(), m_root_mutex(), m_collect_mutex(), m_collect(false), m_shutdown(false), m_runner(NULL), m_parity(false) { } // This constructs a garbage collector and starts a collection thread. void GC::initialize() { m_TheGC = new GC; m_TheGC->m_runner = GC_runner::begin(m_TheGC); } // This puts resources into a thread local list before management. void GC::preManage(const GcResource* g) { // We don't manage if there's no invoker. This results in // bad memory management, but not a segfault. if (GcManageInvoke.get() == NULL) { GcManageInvoke.reset(new bool(false)); } if (!(*GcManageInvoke)) return; // Nothing to do for unmanaged memory. if (GcPendingList.get() == NULL) { GcPendingList.reset(new GcResourceList); } GcPendingList->push_front(g); } // This puts resources under management. void GC::manage(const GcResource* g) { GC *gc = getGC(); // If we don't lock the addition list, how do we know this is safe? { // So we lock it. boost::mutex::scoped_lock aLock(gc->m_res_mutex); gc->m_res_list_add.push_back(g); ++gc->m_new_additions; } if (gc->m_new_additions > mAutoTriggerSize) GC::collectSoon(); } // This puts a list of resources under management. void GC::manage(GcResourceList *pList, GcResourceList::iterator iter_end) { GC *gc = getGC(); { // Scope for locking. boost::mutex::scoped_lock aLock(gc->m_res_mutex); // Count them, for new addition tracking. SPEED: Eliminate this. for (GcResourceList::iterator i = pList->begin(); i != iter_end; ++i) ++gc->m_new_additions; gc->m_res_list_add.splice(gc->m_res_list_add.end(), *pList, pList->begin(), iter_end); } if (gc->m_new_additions > mAutoTriggerSize) GC::collectSoon(); } // Set the collector to run. void GC::collectSoon() { GC *gc = getGC(); gc->m_collect = true; } // Destroy everything we can lay our hands on. void GC::obliterateResources(bool permanent) { GC *g = getGC(); boost::mutex::scoped_lock aLock(g->m_res_mutex); g->m_shutdown = true; if (g->m_runner) g->m_runner->join(); delete g->m_runner; std::list::iterator i; for (i = g->m_res_list_mark.begin(); i != g->m_res_list_mark.end(); ++i) { delete *i; } for (i = g->m_res_list_add.begin(); i != g->m_res_list_add.end(); ++i) { delete *i; } g->m_res_list_add.erase(g->m_res_list_add.begin(), g->m_res_list_add.end()); g->m_res_list_mark.erase(g->m_res_list_mark.begin(), g->m_res_list_mark.end()); g->m_new_additions = 0; if (!permanent) { g->m_shutdown = false; g->m_runner = GC_runner::begin(g); } } // Add a root to the garbage collector. void GC::addRoot(const GcResource* g) { GC *gc = getGC(); { boost::mutex::scoped_lock aLock(gc->m_root_mutex); gc->m_root_list.push_back(g); } } // Remove a root from the garbage collector. void GC::removeRoot(const GcResource* g) { GC *gc = getGC(); { boost::mutex::scoped_lock aLock(gc->m_root_mutex); std::list::iterator i; i = std::find(gc->m_root_list.begin(), gc->m_root_list.end(), g); if (i != gc->m_root_list.end()) gc->m_root_list.erase(i); } } // Pause the GC, as explained in the header. void GC::pause() { GC *g = getGC(); boost::mutex::scoped_lock aLock(g->m_res_mutex); g->m_shutdown = true; if (g->m_runner) g->m_runner->join(); delete g->m_runner; } // Resume the GC, after a pause. void GC::resume() { GC *g = getGC(); if (g->m_runner) return; // Already running. { // Scoping. boost::mutex::scoped_lock aLock(g->m_collect_mutex); g->m_shutdown = false; g->m_runner = GC_runner::begin(g); } } // Add a one-time use root. void GC::addTouch(const GcResource* g) { addRoot(g); } // Collect garbage. void GC::collect() { boost::mutex::scoped_lock bLock(m_collect_mutex); m_parity = !m_parity; // To avoid recursive markings. m_collect = false; { // Scope for quick unlocking. boost::mutex::scoped_lock aLock(m_res_mutex); // Splice from the new additions onto the known ones. // This does not invalidate the iterators of the new // additions -- it moves them from one list to the other. m_res_list_mark.splice(m_res_list_mark.end(), m_res_list_add, m_res_list_add.begin(), m_res_list_add.end()); m_new_additions = 0; } // The lock falls out of scope, allowing new additions while we work. markReachableResources(); cleanUnreachableResources(); } // Mark all of the resources which can be reached from the roots. void GC::markReachableResources() { std::list::iterator i; i = m_root_list.begin(); while (i != m_root_list.end()) { (*i)->markReachable(m_parity); // Remove any temporary roots. if (!(*i)->m_is_a_root) { (*i)->m_touched = false; { boost::mutex::scoped_lock aLock(m_root_mutex); i = m_root_list.erase(i); } } else ++i; } } // Destroy any resources that weren't reached in marking. void GC::cleanUnreachableResources() { std::list::iterator i; for (i = m_res_list_mark.begin(); i != m_res_list_mark.end(); /**/) { if (!(*i)->isReachable()) { delete *i; i = m_res_list_mark.erase(i); } else { (*i)->clearReachable(); ++i; } } } GcResource::~GcResource() { if (isRoot()) disableRoot(); } } // namespace gnash