diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/LiquidityBoundsService.java b/backend/src/main/java/de/cotto/lndmanagej/service/LiquidityBoundsService.java index bd8dd9a2..a8dd4d37 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/LiquidityBoundsService.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/LiquidityBoundsService.java @@ -22,6 +22,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiFunction; import static de.cotto.lndmanagej.configuration.PickhardtPaymentsConfigurationSettings.LIQUIDITY_INFORMATION_MAX_AGE; @@ -34,6 +35,7 @@ public class LiquidityBoundsService { private static final Duration DEFAULT_MAX_AGE = Duration.of(10, ChronoUnit.MINUTES); private final MissionControlService missionControlService; private final Map entries; + private final ReentrantLock entriesLock = new ReentrantLock(); private final ConfigurationService configurationService; private final LoadingCache maxAgeCache = new CacheBuilder() .withRefresh(Duration.ofSeconds(5)) @@ -107,9 +109,12 @@ public class LiquidityBoundsService { BiFunction> function ) { TwoPubkeys twoPubkeys = new TwoPubkeys(source, target); - synchronized (this.entries) { + entriesLock.lock(); + try { Optional updated = function.apply(getInfo(twoPubkeys), amount); updated.ifPresent(liquidityBounds -> setInfo(twoPubkeys, liquidityBounds)); + } finally { + entriesLock.unlock(); } } diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/OnlinePeersService.java b/backend/src/main/java/de/cotto/lndmanagej/service/OnlinePeersService.java index d7f4a0af..4b288c3a 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/OnlinePeersService.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/OnlinePeersService.java @@ -122,7 +122,7 @@ public class OnlinePeersService { } private int getRoundedPercentage(Duration total, Duration offline) { - return (int) (offline.getSeconds() * 100.0 / total.getSeconds()); + return (int) (offline.toSeconds() * 100.0 / total.toSeconds()); } private int getChangesWithoutCache(Pubkey pubkey) { diff --git a/balances/src/main/java/de/cotto/lndmanagej/balances/persistence/BalancesDaoImpl.java b/balances/src/main/java/de/cotto/lndmanagej/balances/persistence/BalancesDaoImpl.java index bc4d0f07..463a6634 100644 --- a/balances/src/main/java/de/cotto/lndmanagej/balances/persistence/BalancesDaoImpl.java +++ b/balances/src/main/java/de/cotto/lndmanagej/balances/persistence/BalancesDaoImpl.java @@ -84,7 +84,7 @@ class BalancesDaoImpl implements BalancesDao { if (!first || open) { long satoshis = balances.balanceInformation().localBalance().satoshis(); Duration duration = Duration.between(timestamp, previous); - long minutes = duration.getSeconds() / 60; + long minutes = duration.toSeconds() / 60; totalSatoshis += satoshis * minutes; totalMinutes += minutes; } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 36317c32..2db6359e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,7 +8,7 @@ repositories { dependencies { implementation(platform("de.c-otto.lndmanagej:platform")) - implementation("de.c-otto:java-conventions:2024.06.07") + implementation("de.c-otto:java-conventions:2025.02.16") implementation("org.springframework.boot:spring-boot-gradle-plugin") implementation("com.google.protobuf:protobuf-gradle-plugin") } diff --git a/forwarding-history/src/main/java/de/cotto/lndmanagej/forwardinghistory/persistence/ForwardingEventsDaoImpl.java b/forwarding-history/src/main/java/de/cotto/lndmanagej/forwardinghistory/persistence/ForwardingEventsDaoImpl.java index 4fbc454c..a19c27df 100644 --- a/forwarding-history/src/main/java/de/cotto/lndmanagej/forwardinghistory/persistence/ForwardingEventsDaoImpl.java +++ b/forwarding-history/src/main/java/de/cotto/lndmanagej/forwardinghistory/persistence/ForwardingEventsDaoImpl.java @@ -54,6 +54,6 @@ public class ForwardingEventsDaoImpl implements ForwardingEventsDao { } private long getAfterEpochMilliSeconds(Duration maxAge) { - return Instant.now().toEpochMilli() - maxAge.getSeconds() * 1_000; + return Instant.now().toEpochMilli() - maxAge.toSeconds() * 1_000; } } diff --git a/gradle/meta-plugins/platform/build.gradle.kts b/gradle/meta-plugins/platform/build.gradle.kts index 1b113890..793e5c4d 100644 --- a/gradle/meta-plugins/platform/build.gradle.kts +++ b/gradle/meta-plugins/platform/build.gradle.kts @@ -24,5 +24,6 @@ dependencies { api("io.grpc:grpc-stub:$grpcVersion") api("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") api("io.vavr:vavr:0.10.4") + api("javax.annotation:javax.annotation-api:1.3.2") } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b415..e18bc253 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java b/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java index 2da3147f..b3972ba9 100644 --- a/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java +++ b/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java @@ -76,6 +76,6 @@ public class SettledInvoicesDaoImpl implements SettledInvoicesDao { } private long getAfterEpochSeconds(Duration maxAge) { - return Instant.now().toEpochMilli() / 1_000 - maxAge.getSeconds(); + return Instant.now().toEpochMilli() / 1_000 - maxAge.toSeconds(); } } diff --git a/payments/src/main/java/de/cotto/lndmanagej/payments/Payments.java b/payments/src/main/java/de/cotto/lndmanagej/payments/Payments.java index 3ee70ec8..b41a70fd 100644 --- a/payments/src/main/java/de/cotto/lndmanagej/payments/Payments.java +++ b/payments/src/main/java/de/cotto/lndmanagej/payments/Payments.java @@ -9,12 +9,13 @@ import java.util.List; import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; @Component public class Payments { private final GrpcPayments grpcPayments; private final PaymentsDao dao; - private static final Object SAVE_PAYMENTS_LOCK = new Object(); + private static final ReentrantLock SAVE_PAYMENTS_LOCK = new ReentrantLock(); public Payments(GrpcPayments grpcPayments, PaymentsDao dao) { this.grpcPayments = grpcPayments; @@ -25,9 +26,12 @@ public class Payments { public void loadNewSettledPayments() { List payments; do { - synchronized (SAVE_PAYMENTS_LOCK) { + SAVE_PAYMENTS_LOCK.lock(); + try { payments = grpcPayments.getCompletePaymentsAfter(dao.getIndexOffset()).orElse(List.of()); dao.save(payments); + } finally { + SAVE_PAYMENTS_LOCK.unlock(); } } while (payments.size() == grpcPayments.getLimit()); } @@ -37,7 +41,8 @@ public class Payments { List> paymentOptionals; OptionalLong maxIndex; do { - synchronized (SAVE_PAYMENTS_LOCK) { + SAVE_PAYMENTS_LOCK.lock(); + try { long offsetSettledPayments = dao.getAllSettledIndexOffset(); long offsetKnownPayments = dao.getIndexOffset(); if (offsetKnownPayments == offsetSettledPayments) { @@ -48,6 +53,8 @@ public class Payments { maxIndex = getMaxIndexAllSettled(paymentOptionals); dao.save(paymentOptionals.stream().flatMap(Optional::stream).toList()); maxIndex.ifPresent(dao::setAllSettledIndexOffset); + } finally { + SAVE_PAYMENTS_LOCK.unlock(); } } while (paymentOptionals.size() == grpcPayments.getLimit() && maxIndex.isPresent()); } diff --git a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatus.java b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatus.java index 1a6ff137..9234c5ed 100644 --- a/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatus.java +++ b/pickhardt-payments/src/main/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatus.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.locks.ReentrantLock; public class PaymentStatus extends Flux { private boolean success; @@ -22,6 +23,7 @@ public class PaymentStatus extends Flux { private int numberOfAttemptedRoutes; private final List allMessages; private final List subscriptions; + private final ReentrantLock lock = new ReentrantLock(); public PaymentStatus() { super(); @@ -42,26 +44,35 @@ public class PaymentStatus extends Flux { } public void settled() { - synchronized (this) { + lock.lock(); + try { addMessage("Settled"); success = true; subscriptions.forEach(PaymentStatusSubscription::onComplete); + } finally { + lock.unlock(); } } public void failed(FailureCode failureCode) { - synchronized (this) { + lock.lock(); + try { addMessage("Failed with " + failureCode.toString()); failure = true; subscriptions.forEach(PaymentStatusSubscription::onComplete); + } finally { + lock.unlock(); } } public void failed(String message) { - synchronized (this) { + lock.lock(); + try { addMessage(message); failure = true; subscriptions.forEach(PaymentStatusSubscription::onComplete); + } finally { + lock.unlock(); } } @@ -93,9 +104,12 @@ public class PaymentStatus extends Flux { private void addMessage(String message) { InstantWithString messageWithTimestamp = new InstantWithString(message); - synchronized (this) { + lock.lock(); + try { allMessages.add(messageWithTimestamp); subscriptions.forEach(paymentStatusSubscription -> paymentStatusSubscription.onNext(messageWithTimestamp)); + } finally { + lock.unlock(); } } @@ -133,9 +147,12 @@ public class PaymentStatus extends Flux { public void subscribe(@Nonnull CoreSubscriber subscriber) { PaymentStatusSubscription subscription = new PaymentStatusSubscription(subscriber, new ArrayList<>(allMessages)); - synchronized (this) { + lock.lock(); + try { subscriber.onSubscribe(subscription); subscriptions.add(subscription); + } finally { + lock.unlock(); } if (isFailure() || isSuccess()) { subscription.onComplete(); @@ -164,6 +181,7 @@ public class PaymentStatus extends Flux { } private class PaymentStatusSubscription implements Subscription { + private final ReentrantLock lock = new ReentrantLock(); private final Subscriber subscriber; private final List messagesForSubscriber; private long requested; @@ -179,9 +197,12 @@ public class PaymentStatus extends Flux { @Override public void request(long numberOfMessages) { - synchronized (this) { + lock.lock(); + try { requested += numberOfMessages; sendRequestedMessages(); + } finally { + lock.unlock(); } } @@ -199,9 +220,12 @@ public class PaymentStatus extends Flux { } public void onNext(InstantWithString message) { - synchronized (this) { + lock.lock(); + try { messagesForSubscriber.add(message); sendRequestedMessages(); + } finally { + lock.unlock(); } } diff --git a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatusTest.java b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatusTest.java index a296d60b..8872776e 100644 --- a/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatusTest.java +++ b/pickhardt-payments/src/test/java/de/cotto/lndmanagej/pickhardtpayments/model/PaymentStatusTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test; import java.time.Instant; import java.util.List; import java.util.concurrent.Executors; +import java.util.concurrent.locks.ReentrantLock; import static de.cotto.lndmanagej.ReactiveStreamReader.readAll; import static de.cotto.lndmanagej.ReactiveStreamReader.readMessages; @@ -248,6 +249,10 @@ class PaymentStatusTest { @Test void testEquals() { - EqualsVerifier.simple().forClass(PaymentStatus.class).verify(); + EqualsVerifier.simple() + .forClass(PaymentStatus.class) + .withPrefabValues(ReentrantLock.class, new ReentrantLock(), new ReentrantLock()) + .withIgnoredFields("lock") + .verify(); } } diff --git a/ui/src/main/java/de/cotto/lndmanagej/ui/dto/BalanceInformationModel.java b/ui/src/main/java/de/cotto/lndmanagej/ui/dto/BalanceInformationModel.java index a4a57719..4a96039c 100644 --- a/ui/src/main/java/de/cotto/lndmanagej/ui/dto/BalanceInformationModel.java +++ b/ui/src/main/java/de/cotto/lndmanagej/ui/dto/BalanceInformationModel.java @@ -14,7 +14,8 @@ public record BalanceInformationModel( ) { private static final int TEN_PERCENT = 10; - private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("###"); + private static final ThreadLocal DECIMAL_FORMAT = + ThreadLocal.withInitial(() -> new DecimalFormat("###")); public static final BalanceInformationModel EMPTY = createFromModel(BalanceInformation.EMPTY); public static BalanceInformationModel createFromModel(BalanceInformation balanceInformation) { @@ -44,15 +45,11 @@ public record BalanceInformationModel( public String getOutboundPercentageLabel() { double outbound = getOutboundPercentage(); - synchronized (DECIMAL_FORMAT) { - return outbound < TEN_PERCENT ? "" : DECIMAL_FORMAT.format(outbound) + "%"; - } + return outbound < TEN_PERCENT ? "" : DECIMAL_FORMAT.get().format(outbound) + "%"; } public String getInboundPercentageLabel() { double inbound = getInboundPercentage(); - synchronized (DECIMAL_FORMAT) { - return inbound < TEN_PERCENT ? "" : DECIMAL_FORMAT.format(inbound) + "%"; - } + return inbound < TEN_PERCENT ? "" : DECIMAL_FORMAT.get().format(inbound) + "%"; } }