/** * Indicate that node is least recently used, allowing it to be recycled immediately without * evicting another node. Node must be latched by caller, which is always released by this method. */ void unused(final Node node) { // Node latch is held to ensure that it isn't used for new allocations too soon. In // particular, it might be used for an unevictable allocation. This method would end up // erroneously moving the node back into the usage list. try { acquireExclusive(); } catch (Throwable e) { node.releaseExclusive(); throw e; } try { Node lessUsed = node.mLessUsed; if (lessUsed != null) { Node moreUsed = node.mMoreUsed; lessUsed.mMoreUsed = moreUsed; if (moreUsed == null) { mMostRecentlyUsed = lessUsed; } else { moreUsed.mLessUsed = lessUsed; } node.mLessUsed = null; (node.mMoreUsed = mLeastRecentlyUsed).mLessUsed = node; mLeastRecentlyUsed = node; } else if (mMaxSize != 0) { doMakeEvictableNow(node); } } finally { // The node latch must be released before releasing the usage list latch, to // prevent the node from being immediately promoted to the most recently used by // tryAllocLatchedNode. The caller would acquire the usage list latch, fail to // acquire the node latch, and then the node gets falsely promoted. node.releaseExclusive(); releaseExclusive(); } }
/** * Returns a new or recycled Node instance, latched exclusively, with an undefined id and a clean * state. * * @param trial pass 1 for less aggressive recycle attempt * @param mode MODE_UNEVICTABLE | MODE_NO_EVICT * @return null if no nodes can be recycled or created */ Node tryAllocLatchedNode(int trial, int mode) throws IOException { acquireExclusive(); int limit = mSize; do { Node node = mLeastRecentlyUsed; Node moreUsed; if (node == null || (moreUsed = node.mMoreUsed) == null) { // Grow the cache if possible. if (mSize < mMaxSize) { return doAllocLatchedNode(null, mode); } else if (node == null) { break; } } else { // Move node to the most recently used position. moreUsed.mLessUsed = null; mLeastRecentlyUsed = moreUsed; node.mMoreUsed = null; (node.mLessUsed = mMostRecentlyUsed).mMoreUsed = node; mMostRecentlyUsed = node; } if (!node.tryAcquireExclusive()) { continue; } if (trial == 1) { if (node.mCachedState != CACHED_CLEAN) { if (mSize < mMaxSize) { // Grow the cache instead of evicting. node.releaseExclusive(); return doAllocLatchedNode(null, mode); } else if ((mode & MODE_NO_EVICT) != 0) { node.releaseExclusive(); break; } } // For first attempt, release the latch early to prevent blocking other // allocations while node is evicted. Subsequent attempts retain the latch, // preventing potential allocation starvation. releaseExclusive(); if (node.evict(mDatabase)) { if ((mode & MODE_UNEVICTABLE) != 0) { node.mUsageList.makeUnevictable(node); } // Return with node latch still held. return node; } acquireExclusive(); } else if ((mode & MODE_NO_EVICT) != 0) { if (node.mCachedState != CACHED_CLEAN) { // MODE_NO_EVICT is only used by non-durable database. It ensures that // all clean nodes are least recently used, so no need to keep looking. node.releaseExclusive(); break; } } else { try { if (node.evict(mDatabase)) { if ((mode & MODE_UNEVICTABLE) != 0) { NodeUsageList usageList = node.mUsageList; if (usageList == this) { doMakeUnevictable(node); } else { releaseExclusive(); usageList.makeUnevictable(node); // Return with node latch still held. return node; } } releaseExclusive(); // Return with node latch still held. return node; } } catch (Throwable e) { releaseExclusive(); throw e; } } } while (--limit > 0); releaseExclusive(); return null; }