diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 7fb12d5..b2b26f5 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -1,17 +1,9 @@ -import software.sava.services.core.request_capacity.trackers.ErrorTrackerFactory; -import software.sava.services.core.request_capacity.trackers.HttpErrorTrackerFactory; - module software.sava.core_services { requires systems.comodal.json_iterator; requires java.net.http; requires java.management; - uses ErrorTrackerFactory; - - provides ErrorTrackerFactory with - HttpErrorTrackerFactory; - exports software.sava.services.core.exceptions; exports software.sava.services.core.remote.call; @@ -21,4 +13,9 @@ exports software.sava.services.core.request_capacity.context; exports software.sava.services.core.request_capacity.trackers; + uses software.sava.services.core.request_capacity.trackers.ErrorTrackerFactory; + + provides software.sava.services.core.request_capacity.trackers.ErrorTrackerFactory with + software.sava.services.core.request_capacity.trackers.HttpErrorTrackerFactory; + } diff --git a/core/src/main/java/software/sava/services/core/remote/call/Call.java b/core/src/main/java/software/sava/services/core/remote/call/Call.java index 5922b29..d41861e 100644 --- a/core/src/main/java/software/sava/services/core/remote/call/Call.java +++ b/core/src/main/java/software/sava/services/core/remote/call/Call.java @@ -59,9 +59,8 @@ static Call createCallOrGiveUp(final Supplier> ca static Call createCall(final LoadBalancer loadBalancer, final Function> call, final boolean measureCallTime, - final BalancedErrorHandler balancedErrorHandler, final String retryLogContext) { - return new UncheckedBalancedCall<>(loadBalancer, call, measureCallTime, balancedErrorHandler, retryLogContext); + return new UncheckedBalancedCall<>(loadBalancer, call, measureCallTime, retryLogContext); } static Call createCall(final LoadBalancer loadBalancer, @@ -70,75 +69,15 @@ static Call createCall(final LoadBalancer loadBalancer, final int runtimeWeight, final int maxTryClaim, final boolean measureCallTime, - final BalancedErrorHandler balancedErrorHandler, final String retryLogContext) { return new CourteousBalancedCall<>( loadBalancer, call, callContext, runtimeWeight, maxTryClaim, true, measureCallTime, - balancedErrorHandler, retryLogContext - ); - } - - static Call createCallOrGiveUp(final LoadBalancer loadBalancer, - final Function> call, - final CallContext callContext, - final int runtimeWeight, - final int maxTryClaim, - final boolean measureCallTime, - final BalancedErrorHandler balancedErrorHandler, - final String retryLogContext) { - return new CourteousBalancedCall<>( - loadBalancer, - call, - callContext, runtimeWeight, maxTryClaim, false, measureCallTime, - balancedErrorHandler, retryLogContext - ); - } - - static Call createCall(final LoadBalancer loadBalancer, - final Function> call, - final CallContext callContext, - final int runtimeWeight, - final boolean measureCallTime, - final BalancedErrorHandler balancedErrorHandler, - final String retryLogContext) { - return new GreedyBalancedCall<>( - loadBalancer, - call, - callContext, runtimeWeight, measureCallTime, - balancedErrorHandler, retryLogContext - ); - } - - static Call createCall(final LoadBalancer loadBalancer, - final Function> call, - final boolean measureCallTime, - final String retryLogContext) { - return new UncheckedBalancedCall<>( - loadBalancer, - call, - measureCallTime, - loadBalancer.defaultErrorHandler(), retryLogContext ); } - static Call createCall(final LoadBalancer loadBalancer, - final Function> call, - final CallContext callContext, - final int runtimeWeight, - final int maxTryClaim, - final boolean measureCallTime, - final String retryLogContext) { - return new CourteousBalancedCall<>( - loadBalancer, - call, - callContext, runtimeWeight, maxTryClaim, true, measureCallTime, - loadBalancer.defaultErrorHandler(), retryLogContext - ); - } - static Call createCallOrGiveUp(final LoadBalancer loadBalancer, final Function> call, final CallContext callContext, @@ -150,7 +89,7 @@ static Call createCallOrGiveUp(final LoadBalancer loadBalancer, loadBalancer, call, callContext, runtimeWeight, maxTryClaim, false, measureCallTime, - loadBalancer.defaultErrorHandler(), retryLogContext + retryLogContext ); } @@ -164,7 +103,7 @@ static Call createCall(final LoadBalancer loadBalancer, loadBalancer, call, callContext, runtimeWeight, measureCallTime, - loadBalancer.defaultErrorHandler(), retryLogContext + retryLogContext ); } @@ -178,7 +117,7 @@ static Call createCall(final LoadBalancer loadBalancer, loadBalancer, call, callContext, callContext.callWeight(), maxTryClaim, true, measureCallTime, - loadBalancer.defaultErrorHandler(), retryLogContext + retryLogContext ); } @@ -192,7 +131,7 @@ static Call createCallOrGiveUp(final LoadBalancer loadBalancer, loadBalancer, call, callContext, callContext.callWeight(), maxTryClaim, false, measureCallTime, - loadBalancer.defaultErrorHandler(), retryLogContext + retryLogContext ); } @@ -205,7 +144,7 @@ static Call createCall(final LoadBalancer loadBalancer, loadBalancer, call, callContext, callContext.callWeight(), measureCallTime, - loadBalancer.defaultErrorHandler(), retryLogContext + retryLogContext ); } diff --git a/core/src/main/java/software/sava/services/core/remote/call/CourteousBalancedCall.java b/core/src/main/java/software/sava/services/core/remote/call/CourteousBalancedCall.java index 6cb9d94..f2b161c 100644 --- a/core/src/main/java/software/sava/services/core/remote/call/CourteousBalancedCall.java +++ b/core/src/main/java/software/sava/services/core/remote/call/CourteousBalancedCall.java @@ -20,9 +20,8 @@ final class CourteousBalancedCall extends GreedyBalancedCall { final int maxTryClaim, final boolean forceCall, final boolean measureCallTime, - final BalancedErrorHandler balancedErrorHandler, final String retryLogContext) { - super(loadBalancer, call, callContext, callWeight, measureCallTime, balancedErrorHandler, retryLogContext); + super(loadBalancer, call, callContext, callWeight, measureCallTime, retryLogContext); this.maxTryClaim = maxTryClaim; this.forceCall = forceCall; } diff --git a/core/src/main/java/software/sava/services/core/remote/call/ErrorHandlerConfig.java b/core/src/main/java/software/sava/services/core/remote/call/ErrorHandlerConfig.java index f06fe28..b02df5e 100644 --- a/core/src/main/java/software/sava/services/core/remote/call/ErrorHandlerConfig.java +++ b/core/src/main/java/software/sava/services/core/remote/call/ErrorHandlerConfig.java @@ -19,7 +19,7 @@ public ErrorHandler createHandler() { }; } - private static ErrorHandlerConfig parseConfig(final JsonIterator ji) { + public static ErrorHandlerConfig parseConfig(final JsonIterator ji) { final var parser = new Builder(); ji.testObject(parser); return parser.create(); diff --git a/core/src/main/java/software/sava/services/core/remote/call/GreedyBalancedCall.java b/core/src/main/java/software/sava/services/core/remote/call/GreedyBalancedCall.java index 8737c72..f903593 100644 --- a/core/src/main/java/software/sava/services/core/remote/call/GreedyBalancedCall.java +++ b/core/src/main/java/software/sava/services/core/remote/call/GreedyBalancedCall.java @@ -16,9 +16,8 @@ class GreedyBalancedCall extends UncheckedBalancedCall { final CallContext callContext, final int callWeight, final boolean measureCallTime, - final BalancedErrorHandler balancedErrorHandler, final String retryLogContext) { - super(loadBalancer, call, measureCallTime, balancedErrorHandler, retryLogContext); + super(loadBalancer, call, measureCallTime, retryLogContext); this.callContext = callContext; this.callWeight = callWeight; } diff --git a/core/src/main/java/software/sava/services/core/remote/call/UncheckedBalancedCall.java b/core/src/main/java/software/sava/services/core/remote/call/UncheckedBalancedCall.java index 053d32f..411a0da 100644 --- a/core/src/main/java/software/sava/services/core/remote/call/UncheckedBalancedCall.java +++ b/core/src/main/java/software/sava/services/core/remote/call/UncheckedBalancedCall.java @@ -13,7 +13,6 @@ class UncheckedBalancedCall implements Call { protected final LoadBalancer loadBalancer; protected final Function> call; protected final boolean measureCallTime; - private final BalancedErrorHandler balancedErrorHandler; protected final String retryLogContext; protected BalancedItem next; @@ -21,12 +20,10 @@ class UncheckedBalancedCall implements Call { UncheckedBalancedCall(final LoadBalancer loadBalancer, final Function> call, final boolean measureCallTime, - final BalancedErrorHandler balancedErrorHandler, final String retryLogContext) { this.loadBalancer = loadBalancer; this.call = call; this.measureCallTime = measureCallTime; - this.balancedErrorHandler = balancedErrorHandler; this.retryLogContext = retryLogContext; } @@ -53,7 +50,7 @@ public final R get() { return result; } } catch (final RuntimeException e) { - final long sleep = balancedErrorHandler.onError(this.next, ++errorCount, retryLogContext, e, MILLISECONDS); + final long sleep = this.next.onError(++errorCount, retryLogContext, e, MILLISECONDS); loadBalancer.sort(); if (sleep < 0) { return null; diff --git a/core/src/main/java/software/sava/services/core/remote/load_balance/ArrayLoadBalancer.java b/core/src/main/java/software/sava/services/core/remote/load_balance/ArrayLoadBalancer.java index d6aad63..372d46c 100644 --- a/core/src/main/java/software/sava/services/core/remote/load_balance/ArrayLoadBalancer.java +++ b/core/src/main/java/software/sava/services/core/remote/load_balance/ArrayLoadBalancer.java @@ -1,7 +1,5 @@ package software.sava.services.core.remote.load_balance; -import software.sava.services.core.remote.call.BalancedErrorHandler; - import java.util.List; import java.util.stream.Stream; @@ -9,20 +7,17 @@ final class ArrayLoadBalancer implements LoadBalancer { private final BalancedItem[] items; private final List> itemList; - private final BalancedErrorHandler defaultErrorHandler; private int i; ArrayLoadBalancer(final List> itemList, - final BalancedItem[] items, - final BalancedErrorHandler defaultErrorHandler) { + final BalancedItem[] items) { this.items = items; this.itemList = itemList; - this.defaultErrorHandler = defaultErrorHandler; this.i = -1; } - ArrayLoadBalancer(final BalancedItem[] items, final BalancedErrorHandler defaultErrorHandler) { - this(List.of(items), items, defaultErrorHandler); + ArrayLoadBalancer(final BalancedItem[] items) { + this(List.of(items), items); } @Override @@ -128,9 +123,4 @@ public BalancedItem withContext() { public List> items() { return this.itemList; } - - @Override - public BalancedErrorHandler defaultErrorHandler() { - return defaultErrorHandler; - } } diff --git a/core/src/main/java/software/sava/services/core/remote/load_balance/BalancedItem.java b/core/src/main/java/software/sava/services/core/remote/load_balance/BalancedItem.java index 1d24c02..bb2f021 100644 --- a/core/src/main/java/software/sava/services/core/remote/load_balance/BalancedItem.java +++ b/core/src/main/java/software/sava/services/core/remote/load_balance/BalancedItem.java @@ -1,12 +1,17 @@ package software.sava.services.core.remote.load_balance; +import software.sava.services.core.remote.call.ErrorHandler; import software.sava.services.core.request_capacity.CapacityMonitor; import software.sava.services.core.request_capacity.CapacityState; +import java.util.concurrent.TimeUnit; + public interface BalancedItem { - static BalancedItem createItem(final T item, final CapacityMonitor capacityMonitor) { - return new ItemContext<>(item, capacityMonitor); + static BalancedItem createItem(final T item, + final CapacityMonitor capacityMonitor, + final ErrorHandler errorHandler) { + return new ItemContext<>(item, capacityMonitor, errorHandler); } void sample(final long sample); @@ -36,4 +41,9 @@ default void failed() { default CapacityState capacityState() { return capacityMonitor().capacityState(); } + + long onError(final int errorCount, + final String retryLogContext, + final RuntimeException exception, + final TimeUnit timeUnit); } diff --git a/core/src/main/java/software/sava/services/core/remote/load_balance/ItemContext.java b/core/src/main/java/software/sava/services/core/remote/load_balance/ItemContext.java index 125bfe6..b5ef50b 100644 --- a/core/src/main/java/software/sava/services/core/remote/load_balance/ItemContext.java +++ b/core/src/main/java/software/sava/services/core/remote/load_balance/ItemContext.java @@ -1,8 +1,10 @@ package software.sava.services.core.remote.load_balance; +import software.sava.services.core.remote.call.ErrorHandler; import software.sava.services.core.request_capacity.CapacityMonitor; import java.util.Arrays; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLongArray; @@ -18,6 +20,7 @@ final class ItemContext implements BalancedItem { private final T item; private final CapacityMonitor capacityMonitor; + private final ErrorHandler errorHandler; private final AtomicLong failureCount; private final AtomicLongArray samples; private final AtomicInteger sampleIndex; @@ -25,9 +28,12 @@ final class ItemContext implements BalancedItem { private final long[] localSampleArray; private long skipped; - ItemContext(final T item, final CapacityMonitor capacityMonitor) { + ItemContext(final T item, + final CapacityMonitor capacityMonitor, + final ErrorHandler errorHandler) { this.item = item; this.capacityMonitor = capacityMonitor; + this.errorHandler = errorHandler; this.failureCount = new AtomicLong(); this.samples = new AtomicLongArray(NUM_SAMPLES); this.sampleIndex = new AtomicInteger(-1); @@ -103,4 +109,13 @@ public void selected() { public CapacityMonitor capacityMonitor() { return capacityMonitor; } + + @Override + public long onError(final int errorCount, + final String retryLogContext, + final RuntimeException exception, + final TimeUnit timeUnit) { + failed(); + return errorHandler.onError(errorCount, retryLogContext, exception, timeUnit); + } } diff --git a/core/src/main/java/software/sava/services/core/remote/load_balance/LoadBalancer.java b/core/src/main/java/software/sava/services/core/remote/load_balance/LoadBalancer.java index 93ecbee..4685e8a 100644 --- a/core/src/main/java/software/sava/services/core/remote/load_balance/LoadBalancer.java +++ b/core/src/main/java/software/sava/services/core/remote/load_balance/LoadBalancer.java @@ -7,31 +7,30 @@ public interface LoadBalancer { - static LoadBalancer createBalancer(final BalancedItem item, - final BalancedErrorHandler defaultErrorHandler) { - return new SingletonLoadBalancer<>(item, List.of(item), defaultErrorHandler); + static LoadBalancer createBalancer(final BalancedItem item) { + return new SingletonLoadBalancer<>(item, List.of(item)); } - static LoadBalancer createBalancer(final BalancedItem[] items, - final BalancedErrorHandler defaultErrorHandler) { - return new ArrayLoadBalancer<>(items, defaultErrorHandler); + static LoadBalancer createBalancer(final BalancedItem[] items) { + return items.length == 1 + ? createBalancer(items[0]) + : new ArrayLoadBalancer<>(items); } @SuppressWarnings("unchecked") - static LoadBalancer createBalancer(final List> items, - final BalancedErrorHandler defaultErrorHandler) { - return new ArrayLoadBalancer<>(items, items.toArray(BalancedItem[]::new), defaultErrorHandler); + static LoadBalancer createBalancer(final List> items) { + return createBalancer(items.toArray(BalancedItem[]::new)); } - static LoadBalancer createSortedBalancer(final BalancedItem[] items, - final BalancedErrorHandler defaultErrorHandler) { - return new SortedLoadBalancer<>(items, defaultErrorHandler); + static LoadBalancer createSortedBalancer(final BalancedItem[] items) { + return items.length == 1 + ? createBalancer(items[0]) + : new SortedLoadBalancer<>(items); } @SuppressWarnings("unchecked") - static LoadBalancer createSortedBalancer(final List> items, - final BalancedErrorHandler defaultErrorHandler) { - return createSortedBalancer(items.toArray(BalancedItem[]::new), defaultErrorHandler); + static LoadBalancer createSortedBalancer(final List> items) { + return createSortedBalancer(items.toArray(BalancedItem[]::new)); } int size(); @@ -49,6 +48,4 @@ static LoadBalancer createSortedBalancer(final List> item BalancedItem withContext(); List> items(); - - BalancedErrorHandler defaultErrorHandler(); } diff --git a/core/src/main/java/software/sava/services/core/remote/load_balance/SingletonLoadBalancer.java b/core/src/main/java/software/sava/services/core/remote/load_balance/SingletonLoadBalancer.java index 60085c2..6079887 100644 --- a/core/src/main/java/software/sava/services/core/remote/load_balance/SingletonLoadBalancer.java +++ b/core/src/main/java/software/sava/services/core/remote/load_balance/SingletonLoadBalancer.java @@ -1,13 +1,9 @@ package software.sava.services.core.remote.load_balance; -import software.sava.services.core.remote.call.BalancedErrorHandler; - import java.util.List; import java.util.stream.Stream; -record SingletonLoadBalancer(BalancedItem item, - List> items, - BalancedErrorHandler defaultErrorHandler) implements LoadBalancer { +record SingletonLoadBalancer(BalancedItem item, List> items) implements LoadBalancer { @Override public int size() { diff --git a/core/src/main/java/software/sava/services/core/remote/load_balance/SortedLoadBalancer.java b/core/src/main/java/software/sava/services/core/remote/load_balance/SortedLoadBalancer.java index 25fc6e0..69710d2 100644 --- a/core/src/main/java/software/sava/services/core/remote/load_balance/SortedLoadBalancer.java +++ b/core/src/main/java/software/sava/services/core/remote/load_balance/SortedLoadBalancer.java @@ -1,7 +1,5 @@ package software.sava.services.core.remote.load_balance; -import software.sava.services.core.remote.call.BalancedErrorHandler; - import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -26,14 +24,12 @@ final class SortedLoadBalancer implements LoadBalancer { private volatile BalancedItem[] items; private final BalancedItem[] noSkip; - private final BalancedErrorHandler defaultErrorHandler; private final AtomicInteger i; private final int minTrimmedLength; - SortedLoadBalancer(final BalancedItem[] items, final BalancedErrorHandler defaultErrorHandler) { + SortedLoadBalancer(final BalancedItem[] items) { this.items = items; this.noSkip = Arrays.copyOf(items, items.length); - this.defaultErrorHandler = defaultErrorHandler; this.i = new AtomicInteger(-1); this.minTrimmedLength = items.length >> 1; } @@ -53,11 +49,6 @@ public List> items() { return List.of(this.items); } - @Override - public BalancedErrorHandler defaultErrorHandler() { - return defaultErrorHandler; - } - @Override public T next() { return withContext().item(); diff --git a/core/src/main/java/software/sava/services/core/request_capacity/UriCapacityConfig.java b/core/src/main/java/software/sava/services/core/request_capacity/UriCapacityConfig.java index d820a9d..ba87b43 100644 --- a/core/src/main/java/software/sava/services/core/request_capacity/UriCapacityConfig.java +++ b/core/src/main/java/software/sava/services/core/request_capacity/UriCapacityConfig.java @@ -1,21 +1,25 @@ package software.sava.services.core.request_capacity; +import software.sava.services.core.remote.call.ErrorHandler; +import software.sava.services.core.remote.call.ErrorHandlerConfig; import systems.comodal.jsoniter.FieldBufferPredicate; import systems.comodal.jsoniter.JsonIterator; import systems.comodal.jsoniter.ValueType; import java.net.URI; import java.net.http.HttpResponse; -import java.util.Objects; +import static java.util.Objects.requireNonNullElse; import static systems.comodal.jsoniter.JsonIterator.fieldEquals; -public record UriCapacityConfig(URI endpoint, CapacityConfig capacityConfig) { +public record UriCapacityConfig(URI endpoint, + CapacityConfig capacityConfig, + ErrorHandler errorHandler) { public static UriCapacityConfig parseConfig(final JsonIterator ji) { if (ji.whatIsNext() == ValueType.STRING) { final var endpoint = ji.readString(); - return new UriCapacityConfig(URI.create(endpoint), null); + return new UriCapacityConfig(URI.create(endpoint), null, null); } else { final var parser = new UriCapacityConfig.Builder(); ji.testObject(parser); @@ -24,20 +28,21 @@ public static UriCapacityConfig parseConfig(final JsonIterator ji) { } public ErrorTrackedCapacityMonitor> createMonitor(final String serviceName, - final CapacityConfig defaultConfig) { - return Objects.requireNonNullElse(capacityConfig, defaultConfig).createHttpResponseMonitor(serviceName); + final CapacityConfig defaultCapacityConfig) { + return requireNonNullElse(capacityConfig, defaultCapacityConfig).createHttpResponseMonitor(serviceName); } private static final class Builder implements FieldBufferPredicate { private URI endpoint; private CapacityConfig capacityConfig; + private ErrorHandler errorHandler; private Builder() { } private UriCapacityConfig create() { - return new UriCapacityConfig(endpoint, capacityConfig); + return new UriCapacityConfig(endpoint, capacityConfig, errorHandler); } @Override @@ -49,6 +54,8 @@ public boolean test(final char[] buf, final int offset, final int len, final Jso } } else if (fieldEquals("capacity", buf, offset, len)) { capacityConfig = CapacityConfig.parse(ji); + } else if (fieldEquals("backoff", buf, offset, len)) { + errorHandler = ErrorHandlerConfig.parseConfig(ji).createHandler(); } else { ji.skip(); } diff --git a/solana/build.gradle b/solana/build.gradle index fdea03b..3ab3838 100644 --- a/solana/build.gradle +++ b/solana/build.gradle @@ -8,4 +8,13 @@ dependencies { implementation libs.sava.solana.programs implementation libs.sava.anchor.src.gen implementation libs.sava.anchor.programs +} + +task runSolanaService(type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + mainClass = project.findProperty('serviceMainClass') as String + var args = project.findProperty('jvmArgs'); + if (args != null) { + jvmArgs = List.of((args as String).split('\\s+')) + } } \ No newline at end of file diff --git a/solana/runALTService.sh b/solana/runALTService.sh index 7ba5d4f..9a80079 100755 --- a/solana/runALTService.sh +++ b/solana/runALTService.sh @@ -6,20 +6,11 @@ readonly targetJavaVersion=23 readonly moduleName="software.sava.solana_services" readonly package="software.sava.services.solana.accounts.lookup" readonly mainClass="$package.http.LookupTableWebService" -projectDirectory="$(pwd)" -readonly projectDirectory - -javaArgs=( - '--enable-preview' - '-XX:+UseZGC' - '-Xms256M' - '-Xmx1024M' - '-server' -) screen=0; logLevel="INFO"; configFile=""; +jvmArgs="-server --finalization=disabled -XX:+UseZGC -Xms4096M -Xmx8192M" for arg in "$@" do @@ -37,7 +28,6 @@ do exit 2; ;; esac - javaArgs+=("-D$moduleName.logLevel=$logLevel") ;; screen) @@ -51,6 +41,8 @@ do esac ;; + jvm) jvmArgs="$val";; + tjv | targetJavaVersion) targetJavaVersion="$val";; cf | configFile) configFile="$val";; @@ -73,21 +65,4 @@ if [[ "$javaVersion" -ne "$targetJavaVersion" ]]; then exit 3 fi -./gradlew --stacktrace "-PmainClassName=$mainClass" clean jlink - -vcsRef="$(git rev-parse --short HEAD)" -readonly vcsRef -readonly javaExe="$projectDirectory/build/$vcsRef/bin/java" - -javaArgs+=( - "-D$package.LookupTableServiceConfig=$configFile" - '-m' "$moduleName/$mainClass" -) - -if [[ "$screen" == 0 ]]; then - set -x - "$javaExe" "${javaArgs[@]}" -else - set -x - screen -S "anchor-src-gen" "$javaExe" "${javaArgs[@]}" -fi \ No newline at end of file +./gradlew -q --console=plain --no-daemon :solana:runSolanaService -PserviceMainClass="$mainClass" -PjvmArgs="$jvmArgs -D$moduleName.logLevel=$logLevel -D$package.LookupTableServiceConfig=$configFile" \ No newline at end of file diff --git a/solana/src/main/java/module-info.java b/solana/src/main/java/module-info.java index b3bd425..0b8be51 100644 --- a/solana/src/main/java/module-info.java +++ b/solana/src/main/java/module-info.java @@ -9,7 +9,6 @@ requires software.sava.core; requires jdk.httpserver; requires org.bouncycastle.provider; - requires java.management; exports software.sava.services.solana.accounts.lookup; exports software.sava.services.solana.remote.call; diff --git a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LoadBalancerConfig.java b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LoadBalancerConfig.java new file mode 100644 index 0000000..894e1bc --- /dev/null +++ b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LoadBalancerConfig.java @@ -0,0 +1,72 @@ +package software.sava.services.solana.accounts.lookup; + +import software.sava.services.core.remote.call.ErrorHandler; +import software.sava.services.core.remote.call.ErrorHandlerConfig; +import software.sava.services.core.remote.load_balance.BalancedItem; +import software.sava.services.core.request_capacity.CapacityConfig; +import software.sava.services.core.request_capacity.UriCapacityConfig; +import systems.comodal.jsoniter.FieldBufferPredicate; +import systems.comodal.jsoniter.JsonIterator; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import static systems.comodal.jsoniter.JsonIterator.fieldEquals; + +public record LoadBalancerConfig(CapacityConfig defaultCapacityConfig, + ErrorHandler defaultErrorHandler, + List rpcConfigs) { + + public ArrayList> createItems(final BiFunction> createItem) { + final var items = new ArrayList>(rpcConfigs.size()); + for (final var rpcConfig : rpcConfigs) { + final var item = createItem.apply(this, rpcConfig); + items.add(item); + } + return items; + } + + public static LoadBalancerConfig parse(final JsonIterator ji) { + final var parser = new Builder(); + ji.testObject(parser); + return parser.create(); + } + + private static final class Builder implements FieldBufferPredicate { + + private CapacityConfig defaultCapacityConfig; + private ErrorHandler defaultErrorHandler; + private List rpcConfigs; + + private Builder() { + } + + private LoadBalancerConfig create() { + return new LoadBalancerConfig( + defaultCapacityConfig, + defaultErrorHandler, + rpcConfigs + ); + } + + @Override + public boolean test(final char[] buf, final int offset, final int len, final JsonIterator ji) { + if (fieldEquals("defaultCapacity", buf, offset, len)) { + defaultCapacityConfig = CapacityConfig.parse(ji); + } else if (fieldEquals("defaultBackoff", buf, offset, len)) { + defaultErrorHandler = ErrorHandlerConfig.parseConfig(ji).createHandler(); + } else if (fieldEquals("endpoints", buf, offset, len)) { + final var rpcConfigs = new ArrayList(); + while (ji.readArray()) { + final var rpcConfig = UriCapacityConfig.parseConfig(ji); + rpcConfigs.add(rpcConfig); + } + this.rpcConfigs = rpcConfigs; + } else { + ji.skip(); + } + return true; + } + } +} diff --git a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCache.java b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCache.java index c08b706..b7ab3ad 100644 --- a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCache.java +++ b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCache.java @@ -4,7 +4,6 @@ import software.sava.core.accounts.lookup.AddressLookupTable; import software.sava.core.accounts.meta.LookupTableAccountMeta; import software.sava.rpc.json.http.client.SolanaRpcClient; -import software.sava.services.core.remote.call.BalancedErrorHandler; import software.sava.services.core.remote.load_balance.LoadBalancer; import java.time.Duration; @@ -12,30 +11,18 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; -import static software.sava.services.core.remote.call.ErrorHandler.linearBackoff; -import static software.sava.services.solana.remote.call.RemoteCallUtil.createRpcClientErrorHandler; - public interface LookupTableCache { static LookupTableCache createCache(final ExecutorService executorService, final int initialCapacity, - final LoadBalancer rpcClients, - final BalancedErrorHandler errorHandler) { + final LoadBalancer rpcClients) { return new LookupTableCacheMap( executorService, initialCapacity, rpcClients, - errorHandler, AddressLookupTable.LOOKUP_TABLE_MAX_ADDRESSES); } - static LookupTableCache createCache(final ExecutorService executorService, - final int initialCapacity, - final LoadBalancer rpcClients) { - final var errorHandler = createRpcClientErrorHandler(linearBackoff(1, 21)); - return createCache(executorService, initialCapacity, rpcClients, errorHandler); - } - LoadBalancer rpcClients(); AddressLookupTable getTable(final PublicKey lookupTableKey); diff --git a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCacheMap.java b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCacheMap.java index 20ab7ce..8db4782 100644 --- a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCacheMap.java +++ b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableCacheMap.java @@ -27,7 +27,6 @@ final class LookupTableCacheMap implements LookupTableCache { private final ExecutorService executorService; private final LoadBalancer rpcClients; - private final BalancedErrorHandler balancedErrorHandler; private final ConcurrentHashMap lookupTableCache; private final int defaultMaxAccounts; private final Function, AddressLookupTable> handleResponse; @@ -35,10 +34,8 @@ final class LookupTableCacheMap implements LookupTableCache { LookupTableCacheMap(final ExecutorService executorService, final int initialCapacity, final LoadBalancer rpcClients, - final BalancedErrorHandler balancedErrorHandler, final int defaultMaxAccounts) { this.executorService = executorService; - this.balancedErrorHandler = balancedErrorHandler; this.rpcClients = rpcClients; this.lookupTableCache = new ConcurrentHashMap<>(initialCapacity); this.defaultMaxAccounts = defaultMaxAccounts; @@ -88,7 +85,6 @@ private Call> createFetchLookupTableCall(final P rpcClients, rpcClient -> rpcClient.getAccountInfo(lookupTableKey, AddressLookupTable.FACTORY), CallContext.DEFAULT_CALL_CONTEXT, 1, Integer.MAX_VALUE, true, - balancedErrorHandler, "rpcClient::getAccountInfo" ); } @@ -167,7 +163,6 @@ public LookupTableAccountMeta[] getOrFetchTables(final List lookupTab rpcClients, rpcClient -> rpcClient.getMultipleAccounts(fetchKeys, AddressLookupTable.FACTORY), CallContext.DEFAULT_CALL_CONTEXT, 1, Integer.MAX_VALUE, true, - balancedErrorHandler, "rpcClient::getMultipleAccounts" ).get(); final long fetchedAt = System.currentTimeMillis(); @@ -216,7 +211,6 @@ private void refreshTables(final List fetchKeys) { rpcClients, rpcClient -> rpcClient.getMultipleAccounts(fetchKeys, AddressLookupTable.FACTORY), CallContext.DEFAULT_CALL_CONTEXT, 1, Integer.MAX_VALUE, false, - balancedErrorHandler, "rpcClient::getMultipleAccounts" ).get(); final long fetchedAt = System.currentTimeMillis(); diff --git a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryService.java b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryService.java index 73192e7..15ffd5b 100644 --- a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryService.java +++ b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryService.java @@ -4,8 +4,6 @@ import software.sava.core.accounts.lookup.AddressLookupTable; import software.sava.core.tx.Instruction; import software.sava.core.tx.Transaction; -import software.sava.rpc.json.http.client.SolanaRpcClient; -import software.sava.services.core.remote.call.BalancedErrorHandler; import software.sava.services.core.remote.call.Call; import software.sava.services.core.request_capacity.context.CallContext; import software.sava.solana.programs.clients.NativeProgramClient; @@ -23,14 +21,14 @@ public interface LookupTableDiscoveryService extends Runnable { static LookupTableDiscoveryService createService(final ExecutorService executorService, final LookupTableServiceConfig serviceConfig, - final BalancedErrorHandler balancedErrorHandler, final NativeProgramClient nativeProgramClient) { final var discoveryConfig = serviceConfig.discoveryConfig(); final var loadConfig = discoveryConfig.remoteLoadConfig(); final var altProgram = nativeProgramClient.accounts().addressLookupTableProgram(); final var partitions = new AtomicReferenceArray(NUM_PARTITIONS); + final var rpcClients = serviceConfig.rpcClients(); final var noAuthorityCall = Call.createCall( - serviceConfig.rpcClients(), rpcClient -> rpcClient.getProgramAccounts( + rpcClients, rpcClient -> rpcClient.getProgramAccounts( altProgram, List.of( ACTIVE_FILTER, @@ -40,7 +38,6 @@ static LookupTableDiscoveryService createService(final ExecutorService executorS ), CallContext.DEFAULT_CALL_CONTEXT, 1, Integer.MAX_VALUE, false, - balancedErrorHandler, "rpcClient::getProgramAccounts" ); final var partitionedCallHandlers = new PartitionedLookupTableCallHandler[NUM_PARTITIONS]; @@ -68,7 +65,6 @@ static LookupTableDiscoveryService createService(final ExecutorService executorS ), CallContext.DEFAULT_CALL_CONTEXT, 1, Integer.MAX_VALUE, false, - balancedErrorHandler, "rpcClient::getProgramAccounts" ); partitionedCallHandlers[i] = new PartitionedLookupTableCallHandler( diff --git a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryServiceImpl.java b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryServiceImpl.java index 71aa077..4b9aef4 100644 --- a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryServiceImpl.java +++ b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableDiscoveryServiceImpl.java @@ -15,14 +15,10 @@ import software.sava.core.rpc.Filter; import software.sava.core.tx.Transaction; import software.sava.rpc.json.http.client.SolanaRpcClient; -import software.sava.services.core.remote.call.BalancedErrorHandler; import software.sava.services.core.remote.call.Call; -import software.sava.services.core.remote.load_balance.BalancedItem; import software.sava.services.core.remote.load_balance.LoadBalancer; -import software.sava.services.core.request_capacity.UriCapacityConfig; import software.sava.services.core.request_capacity.context.CallContext; import software.sava.solana.programs.clients.NativeProgramClient; -import systems.comodal.jsoniter.JsonIterator; import java.io.IOException; import java.io.UncheckedIOException; @@ -33,6 +29,7 @@ import java.nio.file.Path; import java.time.Duration; import java.util.Arrays; +import java.util.Iterator; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -50,9 +47,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static software.sava.core.accounts.lookup.AddressLookupTable.AUTHORITY_OPTION_OFFSET; import static software.sava.core.accounts.lookup.AddressLookupTable.DEACTIVATION_SLOT_OFFSET; -import static software.sava.services.core.remote.call.ErrorHandler.linearBackoff; import static software.sava.services.solana.accounts.lookup.LookupTableCallHandler.BY_UNIQUE_ACCOUNTS_REVERSED; -import static software.sava.services.solana.remote.call.RemoteCallUtil.createRpcClientErrorHandler; final class LookupTableDiscoveryServiceImpl implements LookupTableDiscoveryService { @@ -150,7 +145,7 @@ private static ScoredTable[] rankTables(final AddressLookupTable[] partition, final Set accounts, final int minScorePerTable, final int limit) { - final ScoredTable[] rankedTables = new ScoredTable[limit]; + final var rankedTables = new ScoredTable[limit]; AddressLookupTable table; int minScore = Integer.MAX_VALUE; @@ -161,7 +156,7 @@ private static ScoredTable[] rankTables(final AddressLookupTable[] partition, for (; i < to; ++i) { table = partition[i]; score = 0; - for (final var pubKey : accounts) { + for (var pubKey : accounts) { if (table.containKey(pubKey)) { ++score; } @@ -186,16 +181,16 @@ private static ScoredTable[] rankTables(final AddressLookupTable[] partition, } else { Arrays.sort(rankedTables); final int removeIndex = limit - 1; - for (; i < to; ++i) { + for (int r; i < to; ++i) { table = partition[i]; score = 0; - for (final var pubKey : accounts) { + for (var pubKey : accounts) { if (table.containKey(pubKey)) { ++score; } } if (score > minScore) { - int r = removeIndex - 1; + r = removeIndex - 1; rankedTables[removeIndex] = rankedTables[r]; for (; r >= 0; --r) { if (score > rankedTables[r].score()) { @@ -212,7 +207,6 @@ private static ScoredTable[] rankTables(final AddressLookupTable[] partition, } } - public AddressLookupTable[] findOptimalSetOfTables(final Set distinctAccounts) { final var allTables = (AddressLookupTable[]) ALL_TABLES.getOpaque(this); final int numTables = allTables.length; @@ -241,8 +235,10 @@ public AddressLookupTable[] findOptimalSetOfTables(final Set distinct PublicKey removed = null; int numRemoved; int t = 0; - for (final var table : scoredTables) { - final var iterator = distinctAccounts.iterator(); + Iterator iterator; + // TODO: mark first removal only to avoid rollback. + for (var table : scoredTables) { + iterator = distinctAccounts.iterator(); numRemoved = 0; do { next = iterator.next(); @@ -467,7 +463,9 @@ public void run() { start = System.currentTimeMillis(); latch.await(); final var duration = Duration.ofMillis(System.currentTimeMillis() - start); + joinPartitions(); + initialized.obtrudeValue(null); final int numTables = IntStream.range(0, NUM_PARTITIONS) @@ -492,7 +490,6 @@ public void run() { private static void test(final NativeProgramClient nativeProgramClient, final LoadBalancer rpcClients, - final BalancedErrorHandler defaultErrorHandler, final ExecutorService executorService, final LookupTableDiscoveryService tableService) { final var feePayer = AccountMeta.createFeePayer(PublicKey.fromBase58Encoded("savaKKJmmwDsHHhxV6G293hrRM4f1p6jv6qUF441QD3")); @@ -503,7 +500,6 @@ private static void test(final NativeProgramClient nativeProgramClient, rpcClients, driftClient::fetchUser, CallContext.DEFAULT_CALL_CONTEXT, 1, Integer.MAX_VALUE, false, - defaultErrorHandler, "driftClient::fetchUser" ).async(executorService); @@ -556,22 +552,12 @@ private static void test(final NativeProgramClient nativeProgramClient, public static void main(final String[] args) throws IOException, InterruptedException { try (final var executorService = Executors.newVirtualThreadPerTaskExecutor()) { try (final var httpClient = HttpClient.newHttpClient()) { - final var serviceConfig = LookupTableServiceConfig.loadConfig(); - final var configFile = Path.of(System.getProperty("software.sava.services.solana.rpcConfigFile")).toAbsolutePath(); - final UriCapacityConfig rpcConfig = UriCapacityConfig.parseConfig(JsonIterator.parse(Files.readAllBytes(configFile))); - final var endpoint = rpcConfig.endpoint(); - final var monitor = rpcConfig.createMonitor(endpoint.getHost(), null); - final var rpcClient = SolanaRpcClient.createClient(rpcConfig.endpoint(), httpClient, monitor.errorTracker()); - final var defaultErrorHandler = createRpcClientErrorHandler( - linearBackoff(1, 21) - ); - final var rpcClients = LoadBalancer.createBalancer(BalancedItem.createItem(rpcClient, monitor), defaultErrorHandler); + final var serviceConfig = LookupTableServiceConfig.loadConfig(httpClient); final var nativeProgramClient = NativeProgramClient.createClient(); final var tableService = LookupTableDiscoveryService.createService( executorService, serviceConfig, - defaultErrorHandler, nativeProgramClient ); executorService.execute(tableService); @@ -580,8 +566,7 @@ public static void main(final String[] args) throws IOException, InterruptedExce if (serviceConfig.discoveryConfig().remoteLoadConfig().reloadDelay() == null) { test( nativeProgramClient, - rpcClients, - defaultErrorHandler, + serviceConfig.rpcClients(), executorService, tableService ); diff --git a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableServiceConfig.java b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableServiceConfig.java index 99dc86e..474a44e 100644 --- a/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableServiceConfig.java +++ b/solana/src/main/java/software/sava/services/solana/accounts/lookup/LookupTableServiceConfig.java @@ -7,12 +7,14 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.net.http.HttpClient; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; -import java.util.Objects; +import static java.util.Objects.requireNonNull; +import static software.sava.services.solana.load_balance.LoadBalanceUtil.createRPCLoadBalancer; import static systems.comodal.jsoniter.JsonIterator.fieldEquals; public record LookupTableServiceConfig(LoadBalancer rpcClients, @@ -20,9 +22,10 @@ public record LookupTableServiceConfig(LoadBalancer rpcClients, Web webConfig, TableCache tableCacheConfig) { - public static LookupTableServiceConfig loadConfig(final Path serviceConfigFile) { + public static LookupTableServiceConfig loadConfig(final Path serviceConfigFile, + final HttpClient httpClient) { try (final var ji = JsonIterator.parse(Files.readAllBytes(serviceConfigFile))) { - final var parser = new Builder(); + final var parser = new Builder(httpClient); ji.testObject(parser); return parser.create(); } catch (final IOException e) { @@ -30,19 +33,21 @@ public static LookupTableServiceConfig loadConfig(final Path serviceConfigFile) } } - public static LookupTableServiceConfig loadConfig() { + public static LookupTableServiceConfig loadConfig(final HttpClient httpClient) { final var serviceConfigFile = Path.of(System.getProperty(LookupTableServiceConfig.class.getName())).toAbsolutePath(); - return loadConfig(serviceConfigFile); + return loadConfig(serviceConfigFile, httpClient); } private static final class Builder implements FieldBufferPredicate { + private final HttpClient httpClient; private LoadBalancer rpcClients; private Discovery discoveryConfig; private Web webConfig; private TableCache tableCacheConfig; - private Builder() { + private Builder(final HttpClient httpClient) { + this.httpClient = httpClient; } private LookupTableServiceConfig create() { @@ -62,8 +67,8 @@ public boolean test(final char[] buf, final int offset, final int len, final Jso webConfig = Web.parse(ji); } else if (fieldEquals("tableCache", buf, offset, len)) { tableCacheConfig = TableCache.parse(ji); -// } else if (fieldEquals("rpcClients", buf, offset, len)) { -// rpcClients = null; + } else if (fieldEquals("rpc", buf, offset, len)) { + rpcClients = createRPCLoadBalancer(LoadBalancerConfig.parse(ji), httpClient); } else { ji.skip(); } @@ -97,7 +102,7 @@ private Builder() { private Discovery create() { return new Discovery( - Objects.requireNonNull(cacheDirectory, "Must provide a cache directory."), + requireNonNull(cacheDirectory, "Must provide a cache directory."), remoteLoad == null ? new RemoteLoad.Builder().create() : remoteLoad, query == null ? new Query.Builder().create() : query ); diff --git a/solana/src/main/java/software/sava/services/solana/accounts/lookup/http/LookupTableWebService.java b/solana/src/main/java/software/sava/services/solana/accounts/lookup/http/LookupTableWebService.java index 3860132..f4e4247 100644 --- a/solana/src/main/java/software/sava/services/solana/accounts/lookup/http/LookupTableWebService.java +++ b/solana/src/main/java/software/sava/services/solana/accounts/lookup/http/LookupTableWebService.java @@ -12,24 +12,18 @@ import java.util.concurrent.Executors; import static java.util.concurrent.TimeUnit.SECONDS; -import static software.sava.services.core.remote.call.ErrorHandler.linearBackoff; -import static software.sava.services.solana.remote.call.RemoteCallUtil.createRpcClientErrorHandler; public final class LookupTableWebService { public static void main(final String[] args) throws IOException, InterruptedException { try (final var executorService = Executors.newVirtualThreadPerTaskExecutor()) { try (final var httpClient = HttpClient.newBuilder().executor(executorService).build()) { - final var serviceConfig = LookupTableServiceConfig.loadConfig(); + final var serviceConfig = LookupTableServiceConfig.loadConfig(httpClient); - final var defaultErrorHandler = createRpcClientErrorHandler( - linearBackoff(1, 21) - ); final var nativeProgramClient = NativeProgramClient.createClient(); final var tableService = LookupTableDiscoveryService.createService( executorService, serviceConfig, - defaultErrorHandler, nativeProgramClient ); executorService.execute(tableService); diff --git a/solana/src/main/java/software/sava/services/solana/load_balance/LoadBalanceUtil.java b/solana/src/main/java/software/sava/services/solana/load_balance/LoadBalanceUtil.java new file mode 100644 index 0000000..2914d2f --- /dev/null +++ b/solana/src/main/java/software/sava/services/solana/load_balance/LoadBalanceUtil.java @@ -0,0 +1,41 @@ +package software.sava.services.solana.load_balance; + +import software.sava.rpc.json.http.client.SolanaRpcClient; +import software.sava.services.core.remote.load_balance.BalancedItem; +import software.sava.services.core.remote.load_balance.LoadBalancer; +import software.sava.services.solana.accounts.lookup.LoadBalancerConfig; + +import java.net.http.HttpClient; + +import static java.util.Objects.requireNonNullElse; + +public final class LoadBalanceUtil { + + public static LoadBalancer createRPCLoadBalancer(final LoadBalancerConfig loadBalancerConfig, final HttpClient httpClient) { + final var defaultCapacityConfig = loadBalancerConfig.defaultCapacityConfig(); + final var defaultErrorHandler = loadBalancerConfig.defaultErrorHandler(); + final var items = loadBalancerConfig.createItems((_, rpcConfig) -> { + final var endpoint = rpcConfig.endpoint(); + final var serviceName = endpoint.getHost(); + final var monitor = rpcConfig.createMonitor( + serviceName, + defaultCapacityConfig + ); + final var client = SolanaRpcClient.createClient( + endpoint, + httpClient, + monitor.errorTracker() + ); + final var errorHandler = requireNonNullElse( + rpcConfig.errorHandler(), + defaultErrorHandler + ); + return BalancedItem.createItem(client, monitor, errorHandler); + }); + return LoadBalancer.createSortedBalancer(items); + } + + + private LoadBalanceUtil() { + } +}