From 928b28135c7714e45b47262c7aebf9baf94a2a6e Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Thu, 9 Oct 2025 17:13:23 +0200 Subject: [PATCH 1/6] paralle set state --- .../ThreadLocalSwapWasmResolverApi.java | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java index dd00a3ea..7bc61f78 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java @@ -3,6 +3,8 @@ import com.spotify.confidence.flags.resolver.v1.ResolveWithStickyRequest; import com.spotify.confidence.shaded.flags.resolver.v1.ResolveFlagsRequest; import com.spotify.confidence.shaded.flags.resolver.v1.ResolveFlagsResponse; +import com.spotify.futures.CompletableFutures; +import java.util.ArrayList; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -52,18 +54,24 @@ public ThreadLocalSwapWasmResolverApi( this.numInstances = Runtime.getRuntime().availableProcessors(); logger.info( "Initialized ThreadLocalSwapWasmResolverApi with {} available processors", numInstances); + final var futures = new ArrayList>(numInstances); + IntStream.range(0, numInstances) .forEach( - i -> { - final var instance = - new SwapWasmResolverApi( - this.flagLogger, - this.currentState, - this.currentAccountId, - this.stickyResolveStrategy, - this.retryStrategy); - resolverInstances.put(i, instance); - }); + i -> + futures.add( + CompletableFuture.runAsync( + () -> { + final var instance = + new SwapWasmResolverApi( + this.flagLogger, + this.currentState, + this.currentAccountId, + this.stickyResolveStrategy, + this.retryStrategy); + resolverInstances.put(i, instance); + }))); + CompletableFutures.allAsList(futures).join(); } /** @@ -75,10 +83,11 @@ public void updateStateAndFlushLogs(byte[] state, String accountId) { this.currentState = state; this.currentAccountId = accountId; - // Update all pre-initialized resolver instances - resolverInstances - .values() - .forEach(resolver -> resolver.updateStateAndFlushLogs(state, accountId)); + final var futures = + resolverInstances.values().stream() + .map(v -> CompletableFuture.runAsync(() -> v.updateStateAndFlushLogs(state, accountId))) + .toList(); + CompletableFutures.allAsList(futures).join(); } /** From 38a21d8ecb0ecff2efbe208eb6457502e320bb4c Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Thu, 9 Oct 2025 19:42:59 +0200 Subject: [PATCH 2/6] check is flushed after resolve so we flush remaining logs --- .../spotify/confidence/SwapWasmResolverApi.java | 14 +++++++++++--- .../com/spotify/confidence/WasmResolveApi.java | 9 +++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java index cb2b4ed0..f8a2d74b 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java @@ -46,9 +46,9 @@ public void updateStateAndFlushLogs(byte[] state, String accountId) { // Get current instance before switching final WasmResolveApi oldInstance = wasmResolverApiRef.getAndSet(newInstance); - // Flush logs from the old instance (this allows it to be GC'd after method completion) if (oldInstance != null) { oldInstance.flushLogs(); + oldInstance.isIsFlushed(true); } logResolveLock.unlock(); } @@ -75,7 +75,8 @@ public CompletableFuture resolveWithSticky( private CompletableFuture resolveWithStickyInternal( ResolveWithStickyRequest request) { - final var response = wasmResolverApiRef.get().resolveWithSticky(request); + final var instance = wasmResolverApiRef.get(); + final var response = instance.resolveWithSticky(request); switch (response.getResolveResultCase()) { case SUCCESS -> { @@ -84,6 +85,9 @@ private CompletableFuture resolveWithStickyInternal( if (!success.getUpdatesList().isEmpty()) { storeUpdates(success.getUpdatesList()); } + if (instance.isFlushed()) { + CompletableFuture.runAsync(instance::flushLogs); + } return CompletableFuture.completedFuture(success.getResponse()); } case MISSING_MATERIALIZATIONS -> { @@ -205,7 +209,11 @@ private ResolveWithStickyRequest handleMissingMaterializations( @Override public ResolveFlagsResponse resolve(ResolveFlagsRequest request) { logResolveLock.lock(); - final var response = wasmResolverApiRef.get().resolve(request); + final var instance = wasmResolverApiRef.get(); + final var response = instance.resolve(request); + if (instance.isFlushed()) { + CompletableFuture.runAsync(instance::flushLogs); + } logResolveLock.unlock(); return response; } diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java index de262764..99003e7e 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java @@ -51,6 +51,7 @@ class WasmResolveApi { // Retry strategy private final RetryStrategy retryStrategy; + private boolean isFlushed; public WasmResolveApi(WasmFlagLogger flagLogger, RetryStrategy retryStrategy) { this.retryStrategy = retryStrategy; @@ -219,6 +220,14 @@ private ImportFunction createImportFunction( }); } + public void isIsFlushed(boolean b) { + this.isFlushed = true; + } + + public boolean isFlushed() { + return isFlushed; + } + private interface ParserFn { T apply(byte[] data) throws InvalidProtocolBufferException; From e572e6a49c3024dc1c9913f53f2faf3415c07df9 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Fri, 10 Oct 2025 12:11:42 +0200 Subject: [PATCH 3/6] move locks out to wasmresolverapi, remove retry strategy, handle isflushedexception --- .../confidence/ExponentialRetryStrategy.java | 65 ----------------- .../LocalResolverServiceFactory.java | 23 +++---- .../spotify/confidence/NoRetryStrategy.java | 11 --- .../OpenFeatureLocalResolveProvider.java | 69 ++----------------- .../com/spotify/confidence/RetryStrategy.java | 17 ----- .../confidence/SwapWasmResolverApi.java | 54 ++++----------- .../ThreadLocalSwapWasmResolverApi.java | 8 +-- .../spotify/confidence/WasmResolveApi.java | 56 ++++++++------- .../java/com/spotify/confidence/TestBase.java | 6 +- .../spotify/confidence/WasmResolveTest.java | 4 +- 10 files changed, 62 insertions(+), 251 deletions(-) delete mode 100644 openfeature-provider-local/src/main/java/com/spotify/confidence/ExponentialRetryStrategy.java delete mode 100644 openfeature-provider-local/src/main/java/com/spotify/confidence/NoRetryStrategy.java delete mode 100644 openfeature-provider-local/src/main/java/com/spotify/confidence/RetryStrategy.java diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/ExponentialRetryStrategy.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/ExponentialRetryStrategy.java deleted file mode 100644 index e7fa75d4..00000000 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/ExponentialRetryStrategy.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.spotify.confidence; - -import com.dylibso.chicory.wasm.ChicoryException; -import java.util.function.Supplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A retry strategy that retries operations with exponential backoff for Chicory WASM runtime - * exceptions. - */ -class ExponentialRetryStrategy implements RetryStrategy { - private static final Logger logger = LoggerFactory.getLogger(ExponentialRetryStrategy.class); - - private final int maxRetries; - private final long initialBackoffMs; - private final double backoffMultiplier; - - /** - * Creates an exponential retry strategy with the specified configuration. - * - * @param maxRetries Maximum number of retries (not including the initial attempt) - * @param initialBackoffMs Initial backoff delay in milliseconds - * @param backoffMultiplier Multiplier for exponential backoff - */ - public ExponentialRetryStrategy(int maxRetries, long initialBackoffMs, double backoffMultiplier) { - this.maxRetries = maxRetries; - this.initialBackoffMs = initialBackoffMs; - this.backoffMultiplier = backoffMultiplier; - } - - /** - * Creates an exponential retry strategy with default configuration (3 retries, 100ms initial). - */ - public ExponentialRetryStrategy() { - this(3, 100, 2.0); - } - - @Override - public T execute(Supplier operation, String operationName) { - RuntimeException lastException = null; - long backoffMs = initialBackoffMs; - - for (int attempt = 0; attempt <= maxRetries; attempt++) { - try { - return operation.get(); - } catch (ChicoryException e) { - logger.warn("{} attempt {} failed: {}", operationName, attempt + 1, e.getMessage()); - lastException = e; - if (attempt < maxRetries) { - try { - Thread.sleep(backoffMs); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - throw new RuntimeException(operationName + " interrupted during retry backoff", ie); - } - backoffMs = (long) (backoffMs * backoffMultiplier); - } - } - } - - throw new RuntimeException( - operationName + " failed after " + (maxRetries + 1) + " attempts", lastException); - } -} diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java index 8db8eda7..eead1065 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java @@ -67,27 +67,22 @@ static FlagResolverService from( ApiSecret apiSecret, String clientSecret, boolean isWasm, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { - return createFlagResolverService( - apiSecret, clientSecret, isWasm, stickyResolveStrategy, retryStrategy); + StickyResolveStrategy stickyResolveStrategy) { + return createFlagResolverService(apiSecret, clientSecret, isWasm, stickyResolveStrategy); } static FlagResolverService from( AccountStateProvider accountStateProvider, String accountId, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { - return createFlagResolverService( - accountStateProvider, accountId, stickyResolveStrategy, retryStrategy); + StickyResolveStrategy stickyResolveStrategy) { + return createFlagResolverService(accountStateProvider, accountId, stickyResolveStrategy); } private static FlagResolverService createFlagResolverService( ApiSecret apiSecret, String clientSecret, boolean isWasm, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { + StickyResolveStrategy stickyResolveStrategy) { final var channel = createConfidenceChannel(); final AuthServiceBlockingStub authService = AuthServiceGrpc.newBlockingStub(channel); final TokenHolder tokenHolder = @@ -116,8 +111,7 @@ private static FlagResolverService createFlagResolverService( wasmFlagLogger, sidecarFlagsAdminFetcher.rawStateHolder().get().toByteArray(), sidecarFlagsAdminFetcher.accountId, - stickyResolveStrategy, - retryStrategy); + stickyResolveStrategy); flagsFetcherExecutor.scheduleAtFixedRate( sidecarFlagsAdminFetcher::reload, pollIntervalSeconds, @@ -173,8 +167,7 @@ private static boolean getFailFast(StickyResolveStrategy stickyResolveStrategy) private static FlagResolverService createFlagResolverService( AccountStateProvider accountStateProvider, String accountId, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { + StickyResolveStrategy stickyResolveStrategy) { final var mode = System.getenv("LOCAL_RESOLVE_MODE"); if (!(mode == null || mode.equals("WASM"))) { throw new RuntimeException("Only WASM mode supported with AccountStateProvider"); @@ -187,7 +180,7 @@ private static FlagResolverService createFlagResolverService( final WasmFlagLogger flagLogger = request -> WriteFlagLogsResponse.getDefaultInstance(); final ResolverApi wasmResolverApi = new ThreadLocalSwapWasmResolverApi( - flagLogger, resolverStateProtobuf, accountId, stickyResolveStrategy, retryStrategy); + flagLogger, resolverStateProtobuf, accountId, stickyResolveStrategy); flagsFetcherExecutor.scheduleAtFixedRate( () -> { wasmResolverApi.updateStateAndFlushLogs(accountStateProvider.provide(), accountId); diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/NoRetryStrategy.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/NoRetryStrategy.java deleted file mode 100644 index de893cb4..00000000 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/NoRetryStrategy.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.spotify.confidence; - -import java.util.function.Supplier; - -/** A retry strategy that doesn't retry - executes the operation once and returns or throws. */ -class NoRetryStrategy implements RetryStrategy { - @Override - public T execute(Supplier operation, String operationName) { - return operation.get(); - } -} diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/OpenFeatureLocalResolveProvider.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/OpenFeatureLocalResolveProvider.java index e5318123..098345c9 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/OpenFeatureLocalResolveProvider.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/OpenFeatureLocalResolveProvider.java @@ -90,28 +90,7 @@ public class OpenFeatureLocalResolveProvider implements FeatureProvider { * @since 0.2.4 */ public OpenFeatureLocalResolveProvider(ApiSecret apiSecret, String clientSecret) { - this(apiSecret, clientSecret, new RemoteResolverFallback(), new NoRetryStrategy()); - } - - /** - * Creates a new OpenFeature provider for local flag resolution with configurable sticky resolve - * strategy and no retry. - * - *

The provider will automatically determine the resolution mode (WASM or Java) based on the - * {@code LOCAL_RESOLVE_MODE} environment variable, defaulting to WASM mode. - * - * @param apiSecret the API credentials containing client ID and client secret for authenticating - * with the Confidence service. Create using {@code new ApiSecret("client-id", - * "client-secret")} - * @param clientSecret the client secret for your application, used for flag resolution - * authentication. This is different from the API secret and is specific to your application - * configuration - * @param stickyResolveStrategy the strategy to use for handling sticky flag resolution - * @since 0.2.4 - */ - public OpenFeatureLocalResolveProvider( - ApiSecret apiSecret, String clientSecret, StickyResolveStrategy stickyResolveStrategy) { - this(apiSecret, clientSecret, stickyResolveStrategy, new NoRetryStrategy()); + this(apiSecret, clientSecret, new RemoteResolverFallback()); } /** @@ -128,56 +107,25 @@ public OpenFeatureLocalResolveProvider( * authentication. This is different from the API secret and is specific to your application * configuration * @param stickyResolveStrategy the strategy to use for handling sticky flag resolution - * @param retryStrategy the retry strategy for WASM operations (use {@link NoRetryStrategy} to - * disable retries or {@link ExponentialRetryStrategy} for exponential backoff) * @since 0.2.4 */ public OpenFeatureLocalResolveProvider( - ApiSecret apiSecret, - String clientSecret, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { + ApiSecret apiSecret, String clientSecret, StickyResolveStrategy stickyResolveStrategy) { final var env = System.getenv("LOCAL_RESOLVE_MODE"); if (env != null && env.equals("WASM")) { this.flagResolverService = - LocalResolverServiceFactory.from( - apiSecret, clientSecret, true, stickyResolveStrategy, retryStrategy); + LocalResolverServiceFactory.from(apiSecret, clientSecret, true, stickyResolveStrategy); } else if (env != null && env.equals("JAVA")) { this.flagResolverService = - LocalResolverServiceFactory.from( - apiSecret, clientSecret, false, stickyResolveStrategy, retryStrategy); + LocalResolverServiceFactory.from(apiSecret, clientSecret, false, stickyResolveStrategy); } else { this.flagResolverService = - LocalResolverServiceFactory.from( - apiSecret, clientSecret, true, stickyResolveStrategy, retryStrategy); + LocalResolverServiceFactory.from(apiSecret, clientSecret, true, stickyResolveStrategy); } this.stickyResolveStrategy = stickyResolveStrategy; this.clientSecret = clientSecret; } - /** - * To be used for testing purposes only! This constructor allows to inject flags state for testing - * the WASM resolver (no Java supported) No resolve/assign logging is forwarded to production No - * need to supply ApiSecret - * - * @param accountStateProvider a functional interface that provides AccountState instances - * @param clientSecret the flag client key used to filter the flags - * @since 0.2.4 - */ - @VisibleForTesting - public OpenFeatureLocalResolveProvider( - AccountStateProvider accountStateProvider, - String accountId, - String clientSecret, - StickyResolveStrategy stickyResolveStrategy) { - this( - accountStateProvider, - accountId, - clientSecret, - stickyResolveStrategy, - new NoRetryStrategy()); - } - /** * To be used for testing purposes only! This constructor allows to inject flags state for testing * the WASM resolver with full control over retry strategy. @@ -186,7 +134,6 @@ public OpenFeatureLocalResolveProvider( * @param accountId the account ID * @param clientSecret the flag client key used to filter the flags * @param stickyResolveStrategy the strategy to use for handling sticky flag resolution - * @param retryStrategy the retry strategy for WASM operations * @since 0.2.4 */ @VisibleForTesting @@ -194,13 +141,11 @@ public OpenFeatureLocalResolveProvider( AccountStateProvider accountStateProvider, String accountId, String clientSecret, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { + StickyResolveStrategy stickyResolveStrategy) { this.stickyResolveStrategy = stickyResolveStrategy; this.clientSecret = clientSecret; this.flagResolverService = - LocalResolverServiceFactory.from( - accountStateProvider, accountId, stickyResolveStrategy, retryStrategy); + LocalResolverServiceFactory.from(accountStateProvider, accountId, stickyResolveStrategy); } @Override diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/RetryStrategy.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/RetryStrategy.java deleted file mode 100644 index 7cca80db..00000000 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/RetryStrategy.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.spotify.confidence; - -import java.util.function.Supplier; - -/** Strategy for retrying operations that fail with transient errors. */ -interface RetryStrategy { - /** - * Executes an operation with the configured retry strategy. - * - * @param operation The operation to execute - * @param operationName Name of the operation for error messages - * @param Return type of the operation - * @return The result of the operation - * @throws RuntimeException if the operation fails - */ - T execute(Supplier operation, String operationName); -} diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java index f8a2d74b..88ca665f 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java @@ -11,72 +11,53 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; class SwapWasmResolverApi implements ResolverApi { private final AtomicReference wasmResolverApiRef = new AtomicReference<>(); private final StickyResolveStrategy stickyResolveStrategy; private final WasmFlagLogger flagLogger; - private final RetryStrategy retryStrategy; public SwapWasmResolverApi( WasmFlagLogger flagLogger, byte[] initialState, String accountId, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { + StickyResolveStrategy stickyResolveStrategy) { this.stickyResolveStrategy = stickyResolveStrategy; this.flagLogger = flagLogger; - this.retryStrategy = retryStrategy; // Create initial instance - final WasmResolveApi initialInstance = new WasmResolveApi(flagLogger, retryStrategy); + final WasmResolveApi initialInstance = new WasmResolveApi(flagLogger); initialInstance.setResolverState(initialState, accountId); this.wasmResolverApiRef.set(initialInstance); } @Override public void updateStateAndFlushLogs(byte[] state, String accountId) { - logResolveLock.lock(); // Create new instance with updated state - final WasmResolveApi newInstance = new WasmResolveApi(flagLogger, retryStrategy); + final WasmResolveApi newInstance = new WasmResolveApi(flagLogger); newInstance.setResolverState(state, accountId); // Get current instance before switching final WasmResolveApi oldInstance = wasmResolverApiRef.getAndSet(newInstance); - if (oldInstance != null) { oldInstance.flushLogs(); - oldInstance.isIsFlushed(true); } - logResolveLock.unlock(); } @Override - public void close() { - final WasmResolveApi currentInstance = wasmResolverApiRef.get(); - if (currentInstance != null) { - // Note: WasmResolveApi doesn't have a close method, so this is a no-op for now - // but the instance will be GC'd naturally - } - } - - private final ReentrantLock logResolveLock = new ReentrantLock(); + public void close() {} @Override public CompletableFuture resolveWithSticky( ResolveWithStickyRequest request) { - logResolveLock.lock(); - final var response = resolveWithStickyInternal(request); - logResolveLock.unlock(); - return response; - } - - private CompletableFuture resolveWithStickyInternal( - ResolveWithStickyRequest request) { final var instance = wasmResolverApiRef.get(); - final var response = instance.resolveWithSticky(request); + final ResolveWithStickyResponse response; + try { + response = instance.resolveWithSticky(request); + } catch (IsFlushedException e) { + return resolveWithSticky(request); + } switch (response.getResolveResultCase()) { case SUCCESS -> { @@ -85,9 +66,6 @@ private CompletableFuture resolveWithStickyInternal( if (!success.getUpdatesList().isEmpty()) { storeUpdates(success.getUpdatesList()); } - if (instance.isFlushed()) { - CompletableFuture.runAsync(instance::flushLogs); - } return CompletableFuture.completedFuture(success.getResponse()); } case MISSING_MATERIALIZATIONS -> { @@ -103,7 +81,7 @@ private CompletableFuture resolveWithStickyInternal( final var currentRequest = handleMissingMaterializations( request, missingMaterializations.getItemsList(), repository); - return resolveWithStickyInternal(currentRequest); + return resolveWithSticky(currentRequest); } throw new RuntimeException( @@ -208,13 +186,11 @@ private ResolveWithStickyRequest handleMissingMaterializations( @Override public ResolveFlagsResponse resolve(ResolveFlagsRequest request) { - logResolveLock.lock(); final var instance = wasmResolverApiRef.get(); - final var response = instance.resolve(request); - if (instance.isFlushed()) { - CompletableFuture.runAsync(instance::flushLogs); + try { + return instance.resolve(request); + } catch (IsFlushedException e) { + return resolve(request); } - logResolveLock.unlock(); - return response; } } diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java index 7bc61f78..dd10bbf7 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/ThreadLocalSwapWasmResolverApi.java @@ -22,7 +22,6 @@ class ThreadLocalSwapWasmResolverApi implements ResolverApi { LoggerFactory.getLogger(ThreadLocalSwapWasmResolverApi.class); private final WasmFlagLogger flagLogger; private final StickyResolveStrategy stickyResolveStrategy; - private final RetryStrategy retryStrategy; private volatile byte[] currentState; private volatile String currentAccountId; @@ -42,11 +41,9 @@ public ThreadLocalSwapWasmResolverApi( WasmFlagLogger flagLogger, byte[] initialState, String accountId, - StickyResolveStrategy stickyResolveStrategy, - RetryStrategy retryStrategy) { + StickyResolveStrategy stickyResolveStrategy) { this.flagLogger = flagLogger; this.stickyResolveStrategy = stickyResolveStrategy; - this.retryStrategy = retryStrategy; this.currentState = initialState; this.currentAccountId = accountId; @@ -67,8 +64,7 @@ public ThreadLocalSwapWasmResolverApi( this.flagLogger, this.currentState, this.currentAccountId, - this.stickyResolveStrategy, - this.retryStrategy); + this.stickyResolveStrategy); resolverInstances.put(i, instance); }))); CompletableFutures.allAsList(futures).join(); diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java index 99003e7e..22b1f823 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java @@ -26,8 +26,11 @@ import java.io.InputStream; import java.time.Instant; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; +class IsFlushedException extends Exception {} + @FunctionalInterface interface WasmFlagLogger { WriteFlagLogsResponse write(WriteFlagLogsRequest request); @@ -48,13 +51,10 @@ class WasmResolveApi { private final ExportFunction wasmMsgFlushLogs; private final ExportFunction wasmMsgGuestResolve; private final ExportFunction wasmMsgGuestResolveWithSticky; - - // Retry strategy - private final RetryStrategy retryStrategy; + private final ReentrantLock logResolveLock = new ReentrantLock(); private boolean isFlushed; - public WasmResolveApi(WasmFlagLogger flagLogger, RetryStrategy retryStrategy) { - this.retryStrategy = retryStrategy; + public WasmResolveApi(WasmFlagLogger flagLogger) { this.writeFlagLogs = flagLogger; try (InputStream wasmStream = getClass().getClassLoader().getResourceAsStream("wasm/confidence_resolver.wasm")) { @@ -121,31 +121,37 @@ public void setResolverState(byte[] state, String accountId) { } public void flushLogs() { + logResolveLock.lock(); final var voidRequest = Messages.Void.getDefaultInstance(); final var reqPtr = transferRequest(voidRequest); final var respPtr = (int) wasmMsgFlushLogs.apply(reqPtr)[0]; final var request = consumeResponse(respPtr, WriteFlagLogsRequest::parseFrom); final var ignore = writeFlagLogs.write(request); + isFlushed = true; + logResolveLock.unlock(); } - public ResolveWithStickyResponse resolveWithSticky(ResolveWithStickyRequest request) { - return retryStrategy.execute( - () -> { - final int reqPtr = transferRequest(request); - final int respPtr = (int) wasmMsgGuestResolveWithSticky.apply(reqPtr)[0]; - return consumeResponse(respPtr, ResolveWithStickyResponse::parseFrom); - }, - "resolveWithSticky"); + public ResolveWithStickyResponse resolveWithSticky(ResolveWithStickyRequest request) + throws IsFlushedException { + logResolveLock.lock(); + if (isFlushed) { + throw new IsFlushedException(); + } + final int reqPtr = transferRequest(request); + final int respPtr = (int) wasmMsgGuestResolveWithSticky.apply(reqPtr)[0]; + logResolveLock.unlock(); + return consumeResponse(respPtr, ResolveWithStickyResponse::parseFrom); } - public ResolveFlagsResponse resolve(ResolveFlagsRequest request) { - return retryStrategy.execute( - () -> { - final int reqPtr = transferRequest(request); - final int respPtr = (int) wasmMsgGuestResolve.apply(reqPtr)[0]; - return consumeResponse(respPtr, ResolveFlagsResponse::parseFrom); - }, - "resolve"); + public ResolveFlagsResponse resolve(ResolveFlagsRequest request) throws IsFlushedException { + logResolveLock.lock(); + if (isFlushed) { + throw new IsFlushedException(); + } + final int reqPtr = transferRequest(request); + final int respPtr = (int) wasmMsgGuestResolve.apply(reqPtr)[0]; + logResolveLock.unlock(); + return consumeResponse(respPtr, ResolveFlagsResponse::parseFrom); } private T consumeResponse(int addr, ParserFn codec) { @@ -220,14 +226,6 @@ private ImportFunction createImportFunction( }); } - public void isIsFlushed(boolean b) { - this.isFlushed = true; - } - - public boolean isFlushed() { - return isFlushed; - } - private interface ParserFn { T apply(byte[] data) throws InvalidProtocolBufferException; diff --git a/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java b/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java index 65c9e110..72a2d9a7 100644 --- a/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java +++ b/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java @@ -45,11 +45,7 @@ protected TestBase(ResolverState state, boolean isWasm) { if (isWasm) { final var wasmResolverApi = new SwapWasmResolverApi( - request -> null, - desiredState.toProto().toByteArray(), - "", - mockFallback, - new NoRetryStrategy()); + request -> null, desiredState.toProto().toByteArray(), "", mockFallback); resolverServiceFactory = new LocalResolverServiceFactory( wasmResolverApi, resolverState, resolveTokenConverter, mock(), mockFallback); diff --git a/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java b/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java index 41b4fc2e..cd038e98 100644 --- a/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java +++ b/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java @@ -156,8 +156,8 @@ public void testMaterializationRepositoryWhenMaterializationsMissing() { // add @Test and run it locally for load test public void testConcurrentResolveLoadTest() throws InterruptedException { // Test configuration - final int totalResolves = 1000_000; - final int numThreads = 8; + final int totalResolves = 10000_000; + final int numThreads = 10; final int resolvesPerThread = totalResolves / numThreads; // Create the provider using normal exampleState (not with materialization) From 58ade94af26cb2a3518e10d78b6bc3b68994c896 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Fri, 10 Oct 2025 14:30:59 +0200 Subject: [PATCH 4/6] use read write lock, not wait for flush, handle isClosedException, change to isClosed --- .../confidence/GrpcWasmFlagLogger.java | 9 ++- .../LocalResolverServiceFactory.java | 20 +++--- .../confidence/SwapWasmResolverApi.java | 6 +- .../spotify/confidence/WasmResolveApi.java | 67 ++++++++++--------- .../java/com/spotify/confidence/TestBase.java | 2 +- .../spotify/confidence/WasmResolveTest.java | 4 +- 6 files changed, 58 insertions(+), 50 deletions(-) diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/GrpcWasmFlagLogger.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/GrpcWasmFlagLogger.java index 586fe99a..b0315710 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/GrpcWasmFlagLogger.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/GrpcWasmFlagLogger.java @@ -2,7 +2,6 @@ import com.spotify.confidence.shaded.flags.resolver.v1.InternalFlagLoggerServiceGrpc; import com.spotify.confidence.shaded.flags.resolver.v1.WriteFlagLogsRequest; -import com.spotify.confidence.shaded.flags.resolver.v1.WriteFlagLogsResponse; import com.spotify.confidence.shaded.iam.v1.AuthServiceGrpc; import io.grpc.Channel; import io.grpc.ClientInterceptors; @@ -16,7 +15,7 @@ public class GrpcWasmFlagLogger implements WasmFlagLogger { private static final String CONFIDENCE_DOMAIN = "edge-grpc.spotify.com"; private static final Logger logger = LoggerFactory.getLogger(GrpcWasmFlagLogger.class); - private final InternalFlagLoggerServiceGrpc.InternalFlagLoggerServiceBlockingStub stub; + private final InternalFlagLoggerServiceGrpc.InternalFlagLoggerServiceFutureStub stub; public GrpcWasmFlagLogger(ApiSecret apiSecret) { final var channel = createConfidenceChannel(); @@ -27,12 +26,12 @@ public GrpcWasmFlagLogger(ApiSecret apiSecret) { final TokenHolder.Token token = tokenHolder.getToken(); final Channel authenticatedChannel = ClientInterceptors.intercept(channel, new JwtAuthClientInterceptor(tokenHolder)); - this.stub = InternalFlagLoggerServiceGrpc.newBlockingStub(authenticatedChannel); + this.stub = InternalFlagLoggerServiceGrpc.newFutureStub(authenticatedChannel); } @Override - public WriteFlagLogsResponse write(WriteFlagLogsRequest request) { - return stub.writeFlagLogs(request); + public void write(WriteFlagLogsRequest request) { + final var ignore = stub.writeFlagLogs(request); } private static ManagedChannel createConfidenceChannel() { diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java index eead1065..51031a16 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java @@ -42,6 +42,7 @@ class LocalResolverServiceFactory implements ResolverServiceFactory { private static final MetricRegistry metricRegistry = new MetricRegistry(); private static final String CONFIDENCE_DOMAIN = "edge-grpc.spotify.com"; private static final Duration ASSIGN_LOG_INTERVAL = Duration.ofSeconds(10); + private static final Duration POLL_LOG_INTERVAL = Duration.ofSeconds(10); private static final ScheduledExecutorService flagsFetcherExecutor = Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder().setDaemon(true).build()); private static final Duration RESOLVE_INFO_LOG_INTERVAL = Duration.ofMinutes(1); @@ -124,8 +125,8 @@ private static FlagResolverService createFlagResolverService( sidecarFlagsAdminFetcher.rawStateHolder().get().toByteArray(), sidecarFlagsAdminFetcher.accountId); }, - 10, - 10, + POLL_LOG_INTERVAL.getSeconds(), + POLL_LOG_INTERVAL.getSeconds(), TimeUnit.SECONDS); return new WasmFlagResolverService(wasmResolverApi, stickyResolveStrategy); @@ -176,19 +177,22 @@ private static FlagResolverService createFlagResolverService( Optional.ofNullable(System.getenv("CONFIDENCE_RESOLVER_POLL_INTERVAL_SECONDS")) .map(Long::parseLong) .orElse(Duration.ofMinutes(5).toSeconds()); - final byte[] resolverStateProtobuf = accountStateProvider.provide(); + AtomicReference resolverStateProtobuf = + new AtomicReference<>(accountStateProvider.provide()); final WasmFlagLogger flagLogger = request -> WriteFlagLogsResponse.getDefaultInstance(); final ResolverApi wasmResolverApi = new ThreadLocalSwapWasmResolverApi( - flagLogger, resolverStateProtobuf, accountId, stickyResolveStrategy); + flagLogger, resolverStateProtobuf.get(), accountId, stickyResolveStrategy); flagsFetcherExecutor.scheduleAtFixedRate( - () -> { - wasmResolverApi.updateStateAndFlushLogs(accountStateProvider.provide(), accountId); - }, + () -> resolverStateProtobuf.set(accountStateProvider.provide()), pollIntervalSeconds, pollIntervalSeconds, TimeUnit.SECONDS); - + logPollExecutor.scheduleAtFixedRate( + () -> wasmResolverApi.updateStateAndFlushLogs(resolverStateProtobuf.get(), accountId), + POLL_LOG_INTERVAL.getSeconds(), + POLL_LOG_INTERVAL.getSeconds(), + TimeUnit.SECONDS); return new WasmFlagResolverService(wasmResolverApi, stickyResolveStrategy); } diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java index 88ca665f..51ae0876 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/SwapWasmResolverApi.java @@ -41,7 +41,7 @@ public void updateStateAndFlushLogs(byte[] state, String accountId) { // Get current instance before switching final WasmResolveApi oldInstance = wasmResolverApiRef.getAndSet(newInstance); if (oldInstance != null) { - oldInstance.flushLogs(); + oldInstance.close(); } } @@ -55,7 +55,7 @@ public CompletableFuture resolveWithSticky( final ResolveWithStickyResponse response; try { response = instance.resolveWithSticky(request); - } catch (IsFlushedException e) { + } catch (IsClosedException e) { return resolveWithSticky(request); } @@ -189,7 +189,7 @@ public ResolveFlagsResponse resolve(ResolveFlagsRequest request) { final var instance = wasmResolverApiRef.get(); try { return instance.resolve(request); - } catch (IsFlushedException e) { + } catch (IsClosedException e) { return resolve(request); } } diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java index 22b1f823..0d37ed9a 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java @@ -20,20 +20,20 @@ import com.spotify.confidence.shaded.flags.resolver.v1.ResolveFlagsRequest; import com.spotify.confidence.shaded.flags.resolver.v1.ResolveFlagsResponse; import com.spotify.confidence.shaded.flags.resolver.v1.WriteFlagLogsRequest; -import com.spotify.confidence.shaded.flags.resolver.v1.WriteFlagLogsResponse; import com.spotify.confidence.wasm.Messages; import java.io.IOException; import java.io.InputStream; import java.time.Instant; import java.util.List; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; -class IsFlushedException extends Exception {} +class IsClosedException extends Exception {} @FunctionalInterface interface WasmFlagLogger { - WriteFlagLogsResponse write(WriteFlagLogsRequest request); + void write(WriteFlagLogsRequest request); } class WasmResolveApi { @@ -51,8 +51,7 @@ class WasmResolveApi { private final ExportFunction wasmMsgFlushLogs; private final ExportFunction wasmMsgGuestResolve; private final ExportFunction wasmMsgGuestResolveWithSticky; - private final ReentrantLock logResolveLock = new ReentrantLock(); - private boolean isFlushed; + private final ReadWriteLock wasmLock = new ReentrantReadWriteLock(); public WasmResolveApi(WasmFlagLogger flagLogger) { this.writeFlagLogs = flagLogger; @@ -120,38 +119,44 @@ public void setResolverState(byte[] state, String accountId) { consumeResponse(respPtr, Messages.Void::parseFrom); } - public void flushLogs() { - logResolveLock.lock(); - final var voidRequest = Messages.Void.getDefaultInstance(); - final var reqPtr = transferRequest(voidRequest); - final var respPtr = (int) wasmMsgFlushLogs.apply(reqPtr)[0]; - final var request = consumeResponse(respPtr, WriteFlagLogsRequest::parseFrom); - final var ignore = writeFlagLogs.write(request); - isFlushed = true; - logResolveLock.unlock(); + public void close() { + wasmLock.writeLock().lock(); + try { + final var voidRequest = Messages.Void.getDefaultInstance(); + final var reqPtr = transferRequest(voidRequest); + final var respPtr = (int) wasmMsgFlushLogs.apply(reqPtr)[0]; + final var request = consumeResponse(respPtr, WriteFlagLogsRequest::parseFrom); + writeFlagLogs.write(request); + } finally { + wasmLock.writeLock().unlock(); + } } public ResolveWithStickyResponse resolveWithSticky(ResolveWithStickyRequest request) - throws IsFlushedException { - logResolveLock.lock(); - if (isFlushed) { - throw new IsFlushedException(); + throws IsClosedException { + if (!wasmLock.readLock().tryLock()) { + throw new IsClosedException(); + } + try { + final int reqPtr = transferRequest(request); + final int respPtr = (int) wasmMsgGuestResolveWithSticky.apply(reqPtr)[0]; + return consumeResponse(respPtr, ResolveWithStickyResponse::parseFrom); + } finally { + wasmLock.readLock().unlock(); } - final int reqPtr = transferRequest(request); - final int respPtr = (int) wasmMsgGuestResolveWithSticky.apply(reqPtr)[0]; - logResolveLock.unlock(); - return consumeResponse(respPtr, ResolveWithStickyResponse::parseFrom); } - public ResolveFlagsResponse resolve(ResolveFlagsRequest request) throws IsFlushedException { - logResolveLock.lock(); - if (isFlushed) { - throw new IsFlushedException(); + public ResolveFlagsResponse resolve(ResolveFlagsRequest request) throws IsClosedException { + if (!wasmLock.readLock().tryLock()) { + throw new IsClosedException(); + } + try { + final int reqPtr = transferRequest(request); + final int respPtr = (int) wasmMsgGuestResolve.apply(reqPtr)[0]; + return consumeResponse(respPtr, ResolveFlagsResponse::parseFrom); + } finally { + wasmLock.readLock().unlock(); } - final int reqPtr = transferRequest(request); - final int respPtr = (int) wasmMsgGuestResolve.apply(reqPtr)[0]; - logResolveLock.unlock(); - return consumeResponse(respPtr, ResolveFlagsResponse::parseFrom); } private T consumeResponse(int addr, ParserFn codec) { diff --git a/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java b/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java index 72a2d9a7..9687f72c 100644 --- a/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java +++ b/openfeature-provider-local/src/test/java/com/spotify/confidence/TestBase.java @@ -45,7 +45,7 @@ protected TestBase(ResolverState state, boolean isWasm) { if (isWasm) { final var wasmResolverApi = new SwapWasmResolverApi( - request -> null, desiredState.toProto().toByteArray(), "", mockFallback); + request -> {}, desiredState.toProto().toByteArray(), "", mockFallback); resolverServiceFactory = new LocalResolverServiceFactory( wasmResolverApi, resolverState, resolveTokenConverter, mock(), mockFallback); diff --git a/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java b/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java index cd038e98..78a6b622 100644 --- a/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java +++ b/openfeature-provider-local/src/test/java/com/spotify/confidence/WasmResolveTest.java @@ -153,10 +153,10 @@ public void testMaterializationRepositoryWhenMaterializationsMissing() { verify(mockRepository).loadMaterializedAssignmentsForUnit("test-user", "read-mat"); } - // add @Test and run it locally for load test + // add @Test and run it locally for load testing public void testConcurrentResolveLoadTest() throws InterruptedException { // Test configuration - final int totalResolves = 10000_000; + final int totalResolves = 1000_000; final int numThreads = 10; final int resolvesPerThread = totalResolves / numThreads; From 4b550ca32ce66937251af1eb61c28d8c79c7ba19 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Fri, 10 Oct 2025 14:33:58 +0200 Subject: [PATCH 5/6] fixup! use read write lock, not wait for flush, handle isClosedException, change to isClosed --- .../com/spotify/confidence/LocalResolverServiceFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java index 51031a16..802507ce 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/LocalResolverServiceFactory.java @@ -177,7 +177,7 @@ private static FlagResolverService createFlagResolverService( Optional.ofNullable(System.getenv("CONFIDENCE_RESOLVER_POLL_INTERVAL_SECONDS")) .map(Long::parseLong) .orElse(Duration.ofMinutes(5).toSeconds()); - AtomicReference resolverStateProtobuf = + final AtomicReference resolverStateProtobuf = new AtomicReference<>(accountStateProvider.provide()); final WasmFlagLogger flagLogger = request -> WriteFlagLogsResponse.getDefaultInstance(); final ResolverApi wasmResolverApi = From 79039703a0c5412ae7343cc9ee39ce6ecc3f2039 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Fri, 10 Oct 2025 14:47:00 +0200 Subject: [PATCH 6/6] fixup! fixup! use read write lock, not wait for flush, handle isClosedException, change to isClosed --- .../java/com/spotify/confidence/WasmResolveApi.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java index 0d37ed9a..29686bac 100644 --- a/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java +++ b/openfeature-provider-local/src/main/java/com/spotify/confidence/WasmResolveApi.java @@ -120,7 +120,7 @@ public void setResolverState(byte[] state, String accountId) { } public void close() { - wasmLock.writeLock().lock(); + wasmLock.readLock().lock(); try { final var voidRequest = Messages.Void.getDefaultInstance(); final var reqPtr = transferRequest(voidRequest); @@ -128,13 +128,13 @@ public void close() { final var request = consumeResponse(respPtr, WriteFlagLogsRequest::parseFrom); writeFlagLogs.write(request); } finally { - wasmLock.writeLock().unlock(); + wasmLock.readLock().unlock(); } } public ResolveWithStickyResponse resolveWithSticky(ResolveWithStickyRequest request) throws IsClosedException { - if (!wasmLock.readLock().tryLock()) { + if (!wasmLock.writeLock().tryLock()) { throw new IsClosedException(); } try { @@ -142,12 +142,12 @@ public ResolveWithStickyResponse resolveWithSticky(ResolveWithStickyRequest requ final int respPtr = (int) wasmMsgGuestResolveWithSticky.apply(reqPtr)[0]; return consumeResponse(respPtr, ResolveWithStickyResponse::parseFrom); } finally { - wasmLock.readLock().unlock(); + wasmLock.writeLock().unlock(); } } public ResolveFlagsResponse resolve(ResolveFlagsRequest request) throws IsClosedException { - if (!wasmLock.readLock().tryLock()) { + if (!wasmLock.writeLock().tryLock()) { throw new IsClosedException(); } try { @@ -155,7 +155,7 @@ public ResolveFlagsResponse resolve(ResolveFlagsRequest request) throws IsClosed final int respPtr = (int) wasmMsgGuestResolve.apply(reqPtr)[0]; return consumeResponse(respPtr, ResolveFlagsResponse::parseFrom); } finally { - wasmLock.readLock().unlock(); + wasmLock.writeLock().unlock(); } }