Ignore:
Timestamp:
Mar 6, 2007, 8:25:49 PM (18 years ago)
Author:
ggaren
Message:

JavaScriptCore:

Reviewed by Maciej Stachowiak.


Fixed all known crashers exposed by run-webkit-tests --threaded. This covers:

<rdar://problem/4565394> | http://bugs.webkit.org/show_bug.cgi?id=12585

PAC file: after closing a window that contains macworld.com, new window
crashes (KJS::PropertyMap::mark()) (12585)

<rdar://problem/4571215> | http://bugs.webkit.org/show_bug.cgi?id=9211

PAC file: Crash occurs when clicking on the navigation tabs at http://www.businessweek.com/ (9211)

<rdar://problem/4557926>

PAC file: Crash occurs when attempting to view image in slideshow mode
at http://d.smugmug.com/gallery/581716 ( KJS::IfNode::execute (KJS::
ExecState*) + 312) if you use a PAC file

(1) Added some missing JSLocks, along with related ASSERTs.


(2) Fully implemented support for objects that can only be garbage collected
on the main thread. So far, only WebCore uses this. We can add it to API
later if we learn that it's needed.


The implementation uses a "main thread only" flag inside each object. When
collecting on a secondary thread, the Collector does an extra pass through
the heap to mark all flagged objects before sweeping. This solution makes
the common case -- flag lots of objects, but never collect on a secondary
thread -- very fast, even though the uncommon case of garbage collecting
on a secondary thread isn't as fast as it could be. I left some notes
about how to speed it up, if we ever care.


For posterity, here are some things I learned about GC while investigating:


  • Each collect must either mark or delete every heap object. "Zombie" objects, which are neither marked nor deleted, raise these issues:
  • On the next pass, the conservative marking algorithm might mark a zombie, causing it to mark freed objects.
  • The client might try to use a zombie, which would seem live because its finalizer had not yet run.
  • A collect on the main thread is free to delete any object. Presumably, objects allocated on secondary threads have thread-safe finalizers.
  • A collect on a secondary thread must not delete thread-unsafe objects.
  • The mark function must be thread-safe.


Line by line comments:

  • API/JSObjectRef.h: Added comment specifying that the finalize callback may run on any thread.
  • bindings/npruntime.cpp: (_NPN_GetStringIdentifier): Added JSLock.
  • bindings/objc/objc_instance.h:
  • bindings/objc/objc_instance.mm: (ObjcInstance::~ObjcInstance): Use an autorelease pool. The other callers to CFRelease needed one, too, but they were dead code, so I removed them instead. (This fixes a leak seen while running run-webkit-tests --threaded, although I don't think it's specifically a threading issue.)


  • kjs/collector.cpp: (KJS::Collector::collectOnMainThreadOnly): New function. Tells the collector to collect a value only if it's collecting on the main thread. (KJS::Collector::markMainThreadOnlyObjects): New function. Scans the heap for "main thread only" objects and marks them.
  • kjs/date_object.cpp: (KJS::DateObjectImp::DateObjectImp): To make the new ASSERTs happy, allocate our globals on the heap, avoiding a seemingly unsafe destructor call at program exit time.
  • kjs/function_object.cpp: (FunctionPrototype::FunctionPrototype): ditto
  • kjs/interpreter.cpp: (KJS::Interpreter::mark): Removed boolean parameter, which was an incomplete and arguably hackish way to implement markMainThreadOnlyObjects() inside WebCore.
  • kjs/interpreter.h:
  • kjs/identifier.cpp: (KJS::identifierTable): Added some ASSERTs to check for thread safety problems.
  • kjs/list.cpp: Added some ASSERTs to check for thread safety problems. (KJS::allocateListImp): (KJS::List::release): (KJS::List::append): (KJS::List::empty): Make the new ASSERTs happy.
  • kjs/object.h: (KJS::JSObject::JSObject): "m_destructorIsThreadSafe" => "m_collectOnMainThreadOnly". I removed the constructor parameter because m_collectOnMainThreadOnly, like m_marked, is a Collector bit, so only the Collector should set or get it.
  • kjs/object_object.cpp: (ObjectPrototype::ObjectPrototype): Make the ASSERTs happy.
  • kjs/regexp_object.cpp: (RegExpPrototype::RegExpPrototype): ditto
  • kjs/ustring.cpp: Added some ASSERTs to check for thread safety problems. (KJS::UCharReference::ref): (KJS::UString::Rep::createCopying): (KJS::UString::Rep::create): (KJS::UString::Rep::destroy): (KJS::UString::null): Make the new ASSERTs happy.
  • kjs/ustring.h: (KJS::UString::Rep::ref): Added some ASSERTs to check for thread safety problems. (KJS::UString::Rep::deref):
  • kjs/value.h: (KJS::JSCell::JSCell):

JavaScriptGlue:

Reviewed by Maciej Stachowiak.

Fixed all known crashers exposed by run-webkit-tests --threaded while using
a PAC file (for maximum carnage). See JavaScriptCore ChangeLog for
more details.

  • JSBase.cpp: (JSBase::Release): Lock when deleting, because we may be deleting an object (like a JSRun) that holds thread-unsafe data.
  • JSUtils.cpp: (CFStringToUString): Don't lock, because our caller locks. Also, locking inside a function that returns thread-unsafe data by copy will only mask threading problems.
  • JavaScriptGlue.cpp: (JSRunEvaluate): Added missing JSLock. (JSRunCheckSyntax): Converted to JSLock.
  • JavaScriptGlue.xcodeproj/project.pbxproj:

WebCore:

Reviewed by Maciej Stachowiak.

Fixed all known crashers exposed by run-webkit-tests --threaded [*]. See
JavaScriptCore ChangeLog for more details.

  • bindings/js/kjs_binding.cpp: (KJS::domNodesPerDocument): Added thread safety ASSERT. (KJS::ScriptInterpreter::mark): Removed obsolete logic for marking unsafe objects when collecting on a secondary thread. The Collector takes care of this now.
  • bindings/js/kjs_binding.h: (KJS::DOMObject::DOMObject): Used new API for specifying that WebCore objects should be garbage collected on the main thread only.
  • bindings/js/kjs_window.cpp: (KJS::ScheduledAction::execute): Moved JSLock to cover implementedsCall() call, which, for some subclasses, ends up allocating garbage collected objects. (This fix was speculative. I didn't actually see a crash from this.) (KJS::Window::timerFired): Added JSLock around ScheduleAction destruction, since it destroys a KJS::List.
  • bindings/objc/WebScriptObject.mm: (-[WebScriptObject setException:]): Added JSLock. (This fix was speculative. I didn't actually see a crash from this.)
  • bridge/mac/WebCoreScriptDebugger.mm: (-[WebCoreScriptCallFrame evaluateWebScript:]): Added JSLock. (This fix was speculative. I didn't actually see a crash from this.)
  • dom/Document.cpp: (WebCore::Document::~Document): Added JSLock around modification to domNodesPerDocument(), which can be accessed concurrently during garbage collection.
  • dom/Node.cpp: (WebCore::Node::setDocument): ditto.


[*] fast/js/toString-stack-overflow.html is an exception. --threaded mode
crashes this test because it causes the garbage collector to run frequently,
and this test crashes if you happen to garbage collect while it's running.
This is a known issue with stack overflow during the mark phase. It's
not related to threading.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/kjs/collector.cpp

    r19994 r20004  
    109109static CollectorHeap heap = {NULL, 0, 0, 0, NULL, 0, 0, 0, 0};
    110110
     111size_t Collector::mainThreadOnlyObjectCount = 0;
    111112bool Collector::memoryFull = false;
    112113
     
    474475}
    475476
     477void Collector::collectOnMainThreadOnly(JSValue* value)
     478{
     479    ASSERT(value);
     480    ASSERT(JSLock::lockCount() > 0);
     481
     482    if (JSImmediate::isImmediate(value))
     483      return;
     484
     485    JSCell* cell = value->downcast();
     486    cell->m_collectOnMainThreadOnly = true;
     487    ++mainThreadOnlyObjectCount;
     488}
     489
    476490void Collector::markProtectedObjects()
    477491{
     
    485499}
    486500
     501void Collector::markMainThreadOnlyObjects()
     502{
     503    ASSERT(!pthread_main_np());
     504
     505    // Optimization for clients that never register "main thread only" objects.
     506    if (!mainThreadOnlyObjectCount)
     507        return;
     508
     509    // FIXME: We can optimize this marking algorithm by keeping an exact set of
     510    // "main thread only" objects when the "main thread only" object count is
     511    // small. We don't want to keep an exact set all the time, because WebCore
     512    // tends to create lots of "main thread only" objects, and all that set
     513    // thrashing can be expensive.
     514   
     515    size_t count = 0;
     516   
     517    for (size_t block = 0; block < heap.usedBlocks; block++) {
     518        ASSERT(count < mainThreadOnlyObjectCount);
     519       
     520        CollectorBlock* curBlock = heap.blocks[block];
     521        size_t minimumCellsToProcess = curBlock->usedCells;
     522        for (size_t i = 0; (i < minimumCellsToProcess) & (i < CELLS_PER_BLOCK); i++) {
     523            CollectorCell* cell = curBlock->cells + i;
     524            if (cell->u.freeCell.zeroIfFree == 0)
     525                ++minimumCellsToProcess;
     526            else {
     527                JSCell* imp = reinterpret_cast<JSCell*>(cell);
     528                if (imp->m_collectOnMainThreadOnly) {
     529                    if(!imp->marked())
     530                        imp->mark();
     531                    if (++count == mainThreadOnlyObjectCount)
     532                        return;
     533                }
     534            }
     535        }
     536    }
     537
     538    for (size_t cell = 0; cell < heap.usedOversizeCells; cell++) {
     539        ASSERT(count < mainThreadOnlyObjectCount);
     540
     541        JSCell* imp = reinterpret_cast<JSCell*>(heap.oversizeCells[cell]);
     542        if (imp->m_collectOnMainThreadOnly) {
     543            if (!imp->marked())
     544                imp->mark();
     545            if (++count == mainThreadOnlyObjectCount)
     546                return;
     547        }
     548    }
     549}
     550
    487551bool Collector::collect()
    488552{
     
    502566    Interpreter* scr = Interpreter::s_hook;
    503567    do {
    504       scr->mark(currentThreadIsMainThread);
     568      scr->mark();
    505569      scr = scr->next;
    506570    } while (scr != Interpreter::s_hook);
     
    512576  markProtectedObjects();
    513577  List::markProtectedLists();
     578  if (!currentThreadIsMainThread)
     579    markMainThreadOnlyObjects();
    514580
    515581  // SWEEP: delete everything with a zero refcount (garbage) and unmark everything else
     
    531597        if (imp->m_marked) {
    532598          imp->m_marked = false;
    533         } else if (currentThreadIsMainThread || imp->m_destructorIsThreadSafe) {
     599        } else {
    534600          // special case for allocated but uninitialized object
    535           // (We don't need this check earlier because nothing prior this point assumes the object has a valid vptr.)
     601          // (We don't need this check earlier because nothing prior this point
     602          // assumes the object has a valid vptr.)
    536603          if (cell->u.freeCell.zeroIfFree == 0)
    537604            continue;
    538605
     606          ASSERT(currentThreadIsMainThread || !imp->m_collectOnMainThreadOnly);
     607          if (imp->m_collectOnMainThreadOnly)
     608            --mainThreadOnlyObjectCount;
    539609          imp->~JSCell();
    540610          --usedCells;
     
    557627          if (imp->m_marked) {
    558628            imp->m_marked = false;
    559           } else if (currentThreadIsMainThread || imp->m_destructorIsThreadSafe) {
     629          } else {
     630            ASSERT(currentThreadIsMainThread || !imp->m_collectOnMainThreadOnly);
     631            if (imp->m_collectOnMainThreadOnly)
     632              --mainThreadOnlyObjectCount;
    560633            imp->~JSCell();
    561634            --usedCells;
     
    600673    JSCell *imp = (JSCell *)heap.oversizeCells[cell];
    601674   
    602     if (!imp->m_marked && (currentThreadIsMainThread || imp->m_destructorIsThreadSafe)) {
     675    if (imp->m_marked) {
     676      imp->m_marked = false;
     677      cell++;
     678    } else {
     679      ASSERT(currentThreadIsMainThread || !imp->m_collectOnMainThreadOnly);
     680      if (imp->m_collectOnMainThreadOnly)
     681        --mainThreadOnlyObjectCount;
    603682      imp->~JSCell();
    604683#if DEBUG_COLLECTOR
     
    618697        heap.oversizeCells = (CollectorCell **)fastRealloc(heap.oversizeCells, heap.numOversizeCells * sizeof(CollectorCell *));
    619698      }
    620     } else {
    621       imp->m_marked = false;
    622       cell++;
    623699    }
    624700  }
Note: See TracChangeset for help on using the changeset viewer.