LocatableResolver { @Nonnull public CompletableFuture<Integer> getVersion(@Nullable FDBStoreTimer timer) { return runAsync(timer, this::getVersion); } protected  LocatableResolver(@Nonnull FDBDatabase database,
                                @Nullable KeySpacePath path,
                                @Nullable CompletableFuture<ResolvedKeySpacePath> resolvedPath); ScopedValue<T> wrap(T value); @Nonnull FDBDatabase getDatabase(); @Nonnull CompletableFuture<Long> resolve(@Nonnull String name); @Nonnull CompletableFuture<Long> resolve(@Nullable FDBStoreTimer timer, @Nonnull String name); @API(API.Status.UNSTABLE) @Nonnull CompletableFuture<Long> resolve(@Nonnull FDBRecordContext context, @Nonnull String name); @Nonnull CompletableFuture<Long> resolve(@Nonnull String name,
                                           @Nonnull ResolverCreateHooks hooks); @Nonnull CompletableFuture<Long> resolve(@Nullable FDBStoreTimer timer,
                                           @Nonnull String name,
                                           @Nonnull ResolverCreateHooks hooks); @API(API.Status.UNSTABLE) @Nonnull CompletableFuture<Long> resolve(@Nonnull FDBRecordContext context,
                                           @Nonnull String name,
                                           @Nonnull ResolverCreateHooks hooks); @Nonnull CompletableFuture<ResolverResult> resolveWithMetadata(@Nonnull String name,
                                                                 @Nonnull ResolverCreateHooks hooks); @Nonnull CompletableFuture<ResolverResult> resolveWithMetadata(@Nullable FDBStoreTimer timer,
                                                                 @Nonnull String name,
                                                                 @Nonnull ResolverCreateHooks hooks); @API(API.Status.UNSTABLE) @Nonnull CompletableFuture<ResolverResult> resolveWithMetadata(@Nonnull FDBRecordContext context, @Nonnull String name, @Nonnull ResolverCreateHooks hooks); @Nonnull CompletableFuture<ResolverResult> mustResolveWithMetadata(@Nonnull FDBRecordContext context, @Nonnull String name); CompletableFuture<Long> mustResolve(@Nonnull FDBRecordContext context, @Nonnull String name); @Nonnull CompletableFuture<String> reverseLookup(@Nullable FDBStoreTimer timer, @Nonnull Long value); @Nonnull CompletableFuture<Void> exclusiveLock(); @Nonnull CompletableFuture<Void> enableWriteLock(); @Nonnull CompletableFuture<Void> disableWriteLock(); @Nonnull CompletableFuture<Void> retireLayer(); @Nonnull CompletableFuture<Void> incrementVersion(); @Nonnull CompletableFuture<Integer> getVersion(@Nullable FDBStoreTimer timer); @Nonnull CompletableFuture<Boolean> retired(@Nullable FDBStoreTimer timer); @Nonnull CompletableFuture<Boolean> retiredSkipCache(@Nonnull FDBRecordContext context); @Nonnull CompletableFuture<Void> updateMetadataAndVersion(@Nonnull final String key,
                                                            @Nullable final byte[] metadata); @VisibleForTesting final CompletableFuture<Void> setMapping(FDBRecordContext context, String key, Long value); @VisibleForTesting abstract CompletableFuture<Void> setWindow(long count); @Nonnull @API(API.Status.DEPRECATED) @Deprecated Subspace getMappingSubspace(); @Nonnull abstract CompletableFuture<Subspace> getMappingSubspaceAsync(); @Nonnull @API(API.Status.DEPRECATED) @Deprecated Subspace getBaseSubspace(); @Nonnull abstract CompletableFuture<Subspace> getBaseSubspaceAsync(); @Nonnull abstract ResolverResult deserializeValue(byte[] value); @Override String toString(); @Override boolean equals(Object obj); @Override int hashCode();  }
@Test public void testCachingPerDbPerResolver() { KeySpace keySpace = new KeySpace( new KeySpaceDirectory("resolver1", KeyType.STRING, "resolver1"), new KeySpaceDirectory("resolver2", KeyType.STRING, "resolver2")); LocatableResolver resolver1; LocatableResolver resolver2; FDBStoreTimer timer = new FDBStoreTimer(); try (FDBRecordContext context = database.openContext()) { resolver1 = scopedDirectoryGenerator.apply(database, keySpace.path("resolver1").toResolvedPath(context)); resolver2 = scopedDirectoryGenerator.apply(database, keySpace.path("resolver2").toResolvedPath(context)); context.setTimer(timer); for (int i = 0; i < 10; i++) { resolver1.getVersion(context.getTimer()).join(); } assertThat("We only read the value once", timer.getCount(FDBStoreTimer.DetailEvents.RESOLVER_STATE_READ), is(1)); timer = new FDBStoreTimer(); assertThat("count is reset", timer.getCount(FDBStoreTimer.DetailEvents.RESOLVER_STATE_READ), is(0)); context.setTimer(timer); resolver2.getVersion(context.getTimer()).join(); assertThat("We have to read the value for the new resolver", timer.getCount(FDBStoreTimer.DetailEvents.RESOLVER_STATE_READ), is(1)); LocatableResolver newResolver1 = scopedDirectoryGenerator.apply(database, keySpace.path("resolver1").toResolvedPath(context)); timer = new FDBStoreTimer(); assertThat("count is reset", timer.getCount(FDBStoreTimer.DetailEvents.RESOLVER_STATE_READ), is(0)); context.setTimer(timer); for (int i = 0; i < 10; i++) { newResolver1.getVersion(context.getTimer()).join(); } assertThat("we still hit the cache", timer.getCount(FDBStoreTimer.DetailEvents.RESOLVER_STATE_READ), is(0)); } }
@Test public void testGetVersion() { database.setResolverStateRefreshTimeMillis(100); consistently("uninitialized version is 0", () -> { try (FDBRecordContext context = database.openContext()) { return globalScope.getVersion(context.getTimer()).join(); } }, is(0), 200, 10); globalScope.incrementVersion().join(); eventually("version changes to 1", () -> { try (FDBRecordContext context = database.openContext()) { return globalScope.getVersion(context.getTimer()).join(); } }, is(1), 120, 10); globalScope.incrementVersion().join(); eventually("version changes to 2", () -> { try (FDBRecordContext context = database.openContext()) { return globalScope.getVersion(context.getTimer()).join(); } }, is(2), 120, 10); }