private void doUsed(final Node node) { Node moreUsed = node.mMoreUsed; if (moreUsed != null) { Node lessUsed = node.mLessUsed; moreUsed.mLessUsed = lessUsed; if (lessUsed == null) { mLeastRecentlyUsed = moreUsed; } else { lessUsed.mMoreUsed = moreUsed; } node.mMoreUsed = null; (node.mLessUsed = mMostRecentlyUsed).mMoreUsed = node; mMostRecentlyUsed = node; } releaseExclusive(); }
/** * Caller must hold latch, have checked that this list isn't closed, and have checked that * node.mLessUsed is null. */ private void doMakeEvictableNow(final Node node) { Node least = mLeastRecentlyUsed; if (node != least) { node.mMoreUsed = least; if (least == null) { mMostRecentlyUsed = node; } else { least.mLessUsed = node; } mLeastRecentlyUsed = node; } }
/** * 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(); } }
/** Caller must hold latch. */ private void doMakeUnevictable(final Node node) { final Node lessUsed = node.mLessUsed; final Node moreUsed = node.mMoreUsed; if (lessUsed != null) { node.mLessUsed = null; if (moreUsed != null) { node.mMoreUsed = null; lessUsed.mMoreUsed = moreUsed; moreUsed.mLessUsed = lessUsed; } else if (node == mMostRecentlyUsed) { mMostRecentlyUsed = lessUsed; lessUsed.mMoreUsed = null; } } else if (node == mLeastRecentlyUsed) { mLeastRecentlyUsed = moreUsed; if (moreUsed != null) { node.mMoreUsed = null; moreUsed.mLessUsed = null; } else { mMostRecentlyUsed = null; } } }
/** * Allow a Node which was allocated as unevictable to be evictable, starting off as the most * recently used. */ void makeEvictable(final Node node) { acquireExclusive(); try { // Only insert if not closed and if not already in the list. The node latch doesn't // need to be held, and so a concurrent call to the unused method might insert the // node sooner. if (mMaxSize != 0 && node.mMoreUsed == null) { Node most = mMostRecentlyUsed; if (node != most) { node.mLessUsed = most; if (most == null) { mLeastRecentlyUsed = node; } else { most.mMoreUsed = node; } mMostRecentlyUsed = node; } } } finally { releaseExclusive(); } }
/** Must be called when object is no longer referenced. */ void delete() { acquireExclusive(); try { // Prevent new allocations. mMaxSize = 0; Node node = mLeastRecentlyUsed; mLeastRecentlyUsed = null; mMostRecentlyUsed = null; while (node != null) { Node next = node.mMoreUsed; node.mLessUsed = null; node.mMoreUsed = null; // Free memory and make node appear to be evicted. node.delete(mDatabase); node = next; } } finally { releaseExclusive(); } }
/** * Caller must acquire latch, which is released by this method. * * @param arena optional * @param mode MODE_UNEVICTABLE */ private Node doAllocLatchedNode(Object arena, int mode) throws DatabaseException { try { mDatabase.checkClosed(); /*P*/ byte[] page; /*P*/ // [ page = p_calloc(arena, mPageSize); /*P*/ // | /*P*/ // page = mDatabase.mFullyMapped ? p_nonTreePage() : p_calloc(arena, mPageSize); /*P*/ // ] Node node = new Node(this, page); node.acquireExclusive(); mSize++; if ((mode & MODE_UNEVICTABLE) == 0) { Node most = mMostRecentlyUsed; node.mLessUsed = most; if (most == null) { mLeastRecentlyUsed = node; } else { most.mMoreUsed = node; } mMostRecentlyUsed = node; } // Return with node latch still held. return node; } finally { 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; }