docs: private V retrieveCachedValue(boolean loadIfExpired, boolean updateStats) {
  
  		V result;
  		V oldValue = null;
  		Throwable error = null;
  		boolean missed = false;
  
  		// race conditions are allowed
  		this.approxAccessCounter++;
  
  		long now;
  
  		CachedValue<V> cached = cachedValue; // read the cached value
  
  		if (cached != null) {
  
  			long expiresAt = cached.expiresAt;
  			if (expiresAt == Long.MAX_VALUE) {
  				updateStats(false, false);
  				return cached.value;
  			}
  
  			now = U.time();
  			if (now <= expiresAt) {
  				updateStats(false, false);
  				return cached.value;
  			}
  
  		} else {
  			now = U.time();
  		}
  
  		// ----------------- START LOCKS -----------------
  
  		readLock();
  
  		cached = cachedValue; // read the cached value again, inside the read lock
  
  		if (cached == null || now > cached.expiresAt) {
  
  			readUnlock();
  			writeLock();
  
  			// double-check: another thread might have acquired write lock and changed state already
  			if (cached == null || now > cached.expiresAt) {
  
  				V newValue;
  
  				if (loadIfExpired) {
  					try {
  						newValue = loader != null ? loader.map(key) : null;
  
  					} catch (Throwable e) {
  						error = e;
  						newValue = null;
  					}
  
  				} else {
  					newValue = null;
  				}
  
  				oldValue = setValueInsideWriteLock(newValue);
  
  				missed = true;
  			}
  
  			// downgrade by acquiring read lock before releasing write lock
  			readLock();
  			writeUnlock();
  		}
  
  		cached = cachedValue;
  		result = cached != null ? cached.value : null; // read the cached value
  
  		readUnlock();
  
  		releaseOldValue(oldValue); // release the old value - outside of lock
  
  		if (updateStats) {
  			updateStats(missed, error != null);
  		}
  
  		if (error != null) {
  			throw U.rte("Couldn't recalculate the cache value!", error);
  		}
  
  		return result;
  	}