@Override @Nullable public V invoke(K input) { Object value = cache.get(input); if (value != null && value != NotValue.COMPUTING) return WrappedValues.unescapeExceptionOrNull(value); lock.lock(); try { value = cache.get(input); assert value != NotValue.COMPUTING : "Recursion detected on input: " + input + " under " + LockBasedStorageManager.this; if (value != null) return WrappedValues.unescapeExceptionOrNull(value); AssertionError error = null; try { cache.put(input, NotValue.COMPUTING); V typedValue = compute.invoke(input); Object oldValue = cache.put(input, WrappedValues.escapeNull(typedValue)); // This code effectively asserts that oldValue is null // The trickery is here because below we catch all exceptions thrown here, and this is the // only exception that shouldn't be stored // A seemingly obvious way to come about this case would be to declare a special exception // class, but the problem is that // one memoized function is likely to (indirectly) call another, and if this second one // throws this exception, we are screwed if (oldValue != NotValue.COMPUTING) { error = new AssertionError( "Race condition detected on input " + input + ". Old value is " + oldValue + " under " + LockBasedStorageManager.this); throw error; } return typedValue; } catch (Throwable throwable) { if (throwable == error) throw exceptionHandlingStrategy.handleException(throwable); Object oldValue = cache.put(input, WrappedValues.escapeThrowable(throwable)); assert oldValue == NotValue.COMPUTING : "Race condition detected on input " + input + ". Old value is " + oldValue + " under " + LockBasedStorageManager.this; throw exceptionHandlingStrategy.handleException(throwable); } } finally { lock.unlock(); } }