Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 2 files). Show all
Unified · Split
| @@ -1494,6 +1494,7 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc · 1493 unmodified lines | ||
| 1494 | 1494 | |
| 1495 | 1495 | const auto RETRY_ATTEMPT_BATCH = 100; //glm::clamp(qgetenv("RETRY_ATTEMPT_BATCH").toInt(), 1, 50); |
| 1496 | 1496 | const auto RETRY_ATTEMPT_MSECS = 250; //glm::clamp(qgetenv("RETRY_ATTEMPT_MSECS").toInt(), 50, 1000); |
| 1497 | +const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" }; | |
| 1497 | 1498 | |
| 1498 | 1499 | void ScriptEngine::deferLoadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { |
| 1499 | 1500 | const auto needTimer = _pendingLoadEntities.isEmpty(); |
| @@ -1513,12 +1514,18 @@ void ScriptEngine::deferLoadEntityScript(const EntityItemID& entityID, const QSt · 13 unmodified lines | ||
| 1513 | 1514 | if (details.status != EntityScriptStatus::PENDING) { |
| 1514 | 1515 | qCDebug(scriptengine) << "deferLoadEntityScript.retry -- entity status no longer PENDING; " << details.status; |
| 1515 | 1516 | } else { |
| 1516 | − auto &locker = _lockPerUniqueScriptURL[retry.entityScript]; | |
| 1517 | − if (locker.tryLock()) { | |
| 1518 | − locker.unlock(); | |
| 1517 | + if (!_occupiedScriptURLs.contains(retry.entityScript)) { | |
| 1518 | + // the URL is no longer busy with another Entity, so now it's our turn | |
| 1519 | 1519 | loadEntityScript(retry.entityID, retry.entityScript, false); |
| 1520 | 1520 | } else { |
| 1521 | − stillPending << retry; | |
| 1521 | + auto entityID = _occupiedScriptURLs[retry.entityScript]; | |
| 1522 | + if (entityID == BAD_SCRIPT_UUID_PLACEHOLDER) { | |
| 1523 | + // the URL was marked bad by an earlier load attempt, so abort the retry mission | |
| 1524 | + qCDebug(scriptengine) << "... pending load of " << retry.entityID << " cancelled"; | |
| 1525 | + } else { | |
| 1526 | + // the URL is still occupied by another Entity, so add to next retry pass | |
| 1527 | + stillPending << retry; | |
| 1528 | + } | |
| 1522 | 1529 | } |
| 1523 | 1530 | } |
| 1524 | 1531 | } |
| @@ -1543,7 +1550,7 @@ void ScriptEngine::deferLoadEntityScript(const EntityItemID& entityID, const QSt · 18 unmodified lines | ||
| 1543 | 1550 | |
| 1544 | 1551 | // processDeferredEntities gets wrapped as a "regular" JavaScript callback so that it can then |
| 1545 | 1552 | // be scheduled using setInterval per usual -- allowing the script engine to own its lifecycle. |
| 1546 | − // we stil need to call clearInterval manually when done, so its passed to calle() above as a property. | |
| 1553 | + // we stil need to call clearInterval manually when done, so its passed to callee() above as a property. | |
| 1547 | 1554 | |
| 1548 | 1555 | QScriptValue function = newLambdaFunction(processDeferredEntities, "processDeferredEntities-loader"); |
| 1549 | 1556 | QObject *interval = setInterval( function, RETRY_ATTEMPT_MSECS ); |
| @@ -1577,14 +1584,28 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& · 27 unmodified lines | ||
| 1577 | 1584 | updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); |
| 1578 | 1585 | } |
| 1579 | 1586 | |
| 1580 | − // This lock/defer approach allows multiple Entities to boot from the same script URL while still taking | |
| 1587 | + // This "occupied" approach allows multiple Entities to boot from the same script URL while still taking | |
| 1581 | 1588 | // full advantage of cacheable require modules. This only affects Entities literally coming in back-to-back |
| 1582 | 1589 | // before the first one has time to finish loading. |
| 1583 | − auto &locker = _lockPerUniqueScriptURL[entityScript]; | |
| 1584 | − if (!locker.tryLock()) { | |
| 1585 | − qCDebug(scriptengine) << QString("loadEntityScript.deferring[%1] entity: %2 script: %3") | |
| 1586 | − .arg( _pendingLoadEntities.size()).arg(entityID.toString()).arg(entityScript); | |
| 1587 | − deferLoadEntityScript(entityID, entityScript, forceRedownload); | |
| 1590 | + if (!_occupiedScriptURLs.contains(entityScript)) { | |
| 1591 | + // the scriptURL is available -- flag as in-use and proceed | |
| 1592 | + _occupiedScriptURLs[entityScript] = entityID; | |
| 1593 | + } else { | |
| 1594 | + // another Entity is either still booting from this script URL or has failed and marked it "out of order" | |
| 1595 | + auto currentEntityID = _occupiedScriptURLs[entityScript]; | |
| 1596 | + if (currentEntityID == BAD_SCRIPT_UUID_PLACEHOLDER && !forceRedownload) { | |
| 1597 | + // since not force-reloading, assume that the exact same input would produce the exact same output again | |
| 1598 | + // note: this state gets reset with "reload all scripts," leaving/returning to a Domain, clear cache, etc. | |
| 1599 | + qCDebug(scriptengine) << QString("loadEntityScript.cancelled entity: %1 script: %2 (previous script failure)") | |
| 1600 | + .arg(entityID.toString()).arg(entityScript); | |
| 1601 | + updateEntityScriptStatus(entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT, | |
| 1602 | + "Cached Script was marked invalid by previous Entity load failure."); | |
| 1603 | + } else { | |
| 1604 | + // another entity is busy loading from this script URL so wait for them to finish | |
| 1605 | + qCDebug(scriptengine) << QString("loadEntityScript.deferring[%0] entity: %1 script: %2") | |
| 1606 | + .arg( _pendingLoadEntities.size()).arg(entityID.toString()).arg(entityScript); | |
| 1607 | + deferLoadEntityScript(entityID, entityScript, forceRedownload); | |
| 1608 | + } | |
| 1588 | 1609 | return; |
| 1589 | 1610 | } |
| 1590 | 1611 | |
| @@ -1598,19 +1619,18 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& · 7 unmodified lines | ||
| 1598 | 1619 | newDetails.scriptText = entityScript; |
| 1599 | 1620 | newDetails.status = EntityScriptStatus::LOADING; |
| 1600 | 1621 | newDetails.definingSandboxURL = currentSandboxURL; |
| 1601 | − _entityScripts[entityID] = newDetails; | |
| 1622 | + setEntityScriptDetails(entityID, newDetails); | |
| 1602 | 1623 | |
| 1603 | 1624 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 1604 | 1625 | scriptCache->getScriptContents(entityScript, |
| 1605 | − [=, &locker](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) { | |
| 1626 | + [this, entityScript, entityID](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) { | |
| 1606 | 1627 | if (isStopping()) { |
| 1607 | 1628 | #ifdef DEBUG_ENTITY_STATES |
| 1608 | 1629 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- stopping"; |
| 1609 | 1630 | #endif |
| 1610 | − locker.unlock(); | |
| 1611 | 1631 | return; |
| 1612 | 1632 | } |
| 1613 | − executeOnScriptThread([=, &locker]{ | |
| 1633 | + executeOnScriptThread([=]{ | |
| 1614 | 1634 | #ifdef DEBUG_ENTITY_STATES |
| 1615 | 1635 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString(); |
| 1616 | 1636 | #endif |
| @@ -1621,7 +1641,10 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& · 4 unmodified lines | ||
| 1621 | 1641 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; |
| 1622 | 1642 | #endif |
| 1623 | 1643 | } |
| 1624 | − locker.unlock(); | |
| 1644 | + // recheck whether us since may have been set to BAD_SCRIPT_UUID_PLACEHOLDER in entityScriptContentAvailable | |
| 1645 | + if (_occupiedScriptURLs[entityScript] == entityID) { | |
| 1646 | + _occupiedScriptURLs.remove(entityScript); | |
| 1647 | + } | |
| 1625 | 1648 | }); |
| 1626 | 1649 | }, forceRedownload); |
| 1627 | 1650 | } |
| @@ -1666,16 +1689,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 38 unmodified lines | ||
| 1666 | 1689 | newDetails.status = status; |
| 1667 | 1690 | setEntityScriptDetails(entityID, newDetails); |
| 1668 | 1691 | |
| 1669 | − QMutableListIterator<DeferredLoadEntity> i(_pendingLoadEntities); | |
| 1692 | + qCDebug(scriptengine) << "entityScriptContentAvailable -- flagging " << oldDetails.scriptText << " as BAD_SCRIPT_UUID_PLACEHOLDER"; | |
| 1693 | + // flag this scriptURL as unusuable | |
| 1670 | 1694 | // note: we want the *original* entityScript string (not the potentially normalized one from ScriptCache results) |
| 1671 | − const auto entityScript = oldDetails.scriptText; | |
| 1672 | − while (i.hasNext()) { | |
| 1673 | − auto retry = i.next(); | |
| 1674 | − if (retry.entityScript == entityScript) { | |
| 1675 | − qCDebug(scriptengine) << "... pending load of " << retry.entityID << " cancelled"; | |
| 1676 | − i.remove(); | |
| 1677 | − } | |
| 1678 | − } | |
| 1695 | + _occupiedScriptURLs[oldDetails.scriptText] = BAD_SCRIPT_UUID_PLACEHOLDER; | |
| 1679 | 1696 | }; |
| 1680 | 1697 | |
| 1681 | 1698 | // NETWORK / FILESYSTEM ERRORS |
| @@ -1856,7 +1873,7 @@ void ScriptEngine::unloadAllEntityScripts() { · 174 unmodified lines | ||
| 1856 | 1873 | } |
| 1857 | 1874 | _entityScripts.clear(); |
| 1858 | 1875 | emit entityScriptDetailsUpdated(); |
| 1859 | − _lockPerUniqueScriptURL.clear(); | |
| 1876 | + _occupiedScriptURLs.clear(); | |
| 1860 | 1877 | |
| 1861 | 1878 | #ifdef DEBUG_ENGINE_STATE |
| 1862 | 1879 | _debugDump( |