modify ui-bound observable lists on user thread

This commit is contained in:
woodser 2026-01-07 19:20:36 -05:00 committed by woodser
parent 2697225138
commit bf6901c7aa
4 changed files with 137 additions and 98 deletions

View file

@ -1761,7 +1761,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
request.getReserveTxKeyImages(),
verifiedTx.getFee().longValueExact(),
signature); // TODO (woodser): no need for signature to be part of SignedOffer?
addSignedOffer(signedOffer);
UserThread.execute(() -> addSignedOffer(signedOffer));
requestPersistence();
// send response with signature

View file

@ -420,7 +420,12 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
if (reOpen) {
dispute = storedDisputeOptional.get();
} else {
disputeList.add(dispute);
final Dispute finalDispute = dispute;
UserThread.execute(() -> {
synchronized (disputeList.getObservableList()) {
disputeList.add(finalDispute);
}
});
}
}
@ -556,15 +561,17 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
public void removeDisputes(Trade trade) {
T disputeList = getDisputeList();
synchronized (disputeList.getObservableList()) {
for (Dispute dispute : trade.getDisputes()) {
disputeList.remove(dispute);
UserThread.execute(() -> {
T disputeList = getDisputeList();
synchronized (disputeList.getObservableList()) {
for (Dispute dispute : trade.getDisputes()) {
disputeList.remove(dispute);
}
}
}
trade.setDisputeState(Trade.DisputeState.NO_DISPUTE);
clearPendingMessage();
requestPersistence();
trade.setDisputeState(Trade.DisputeState.NO_DISPUTE);
clearPendingMessage();
requestPersistence();
});
}
// arbitrator receives dispute opened message from opener, opener's peer receives from arbitrator
@ -694,7 +701,11 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
if (reOpen) {
trade.setDisputeState(Trade.DisputeState.DISPUTE_OPENED);
} else {
disputeList.add(dispute);
UserThread.execute(() -> {
synchronized (disputeList) {
disputeList.add(dispute);
}
});
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
}
@ -822,9 +833,12 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
dispute = storedDisputeOptional.get();
dispute.reOpen();
} else {
synchronized (disputeList) {
disputeList.add(dispute);
}
final Dispute finalDispute = dispute;
UserThread.execute(() -> {
synchronized (disputeList) {
disputeList.add(finalDispute);
}
});
}
// get trade

View file

@ -1,6 +1,9 @@
package haveno.daemon.grpc;
import com.google.inject.Inject;
import haveno.common.ThreadUtils;
import haveno.common.UserThread;
import haveno.common.config.Config;
import haveno.common.proto.ProtoUtil;
import haveno.core.api.CoreApi;
@ -48,82 +51,102 @@ public class GrpcDisputesService extends DisputesImplBase {
@Override
public void openDispute(OpenDisputeRequest req, StreamObserver<OpenDisputeReply> responseObserver) {
try {
coreApi.openDispute(req.getTradeId(),
() -> {
var reply = OpenDisputeReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
},
(errorMessage, throwable) -> {
log.info("Error in openDispute" + errorMessage);
exceptionHandler.handleErrorMessage(log, errorMessage, responseObserver);
});
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
UserThread.execute(() -> {
ThreadUtils.submitToPool(() -> {
try {
coreApi.openDispute(req.getTradeId(),
() -> {
var reply = OpenDisputeReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
},
(errorMessage, throwable) -> {
log.info("Error in openDispute" + errorMessage);
exceptionHandler.handleErrorMessage(log, errorMessage, responseObserver);
});
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
});
});
}
@Override
public void getDispute(GetDisputeRequest req, StreamObserver<GetDisputeReply> responseObserver) {
try {
var dispute = coreApi.getDispute(req.getTradeId());
var reply = GetDisputeReply.newBuilder()
.setDispute(dispute.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleExceptionAsWarning(log, getClass().getName() + ".getDispute", cause, responseObserver);
}
UserThread.execute(() -> { // offers are updated on user thread
ThreadUtils.submitToPool(() -> {
try {
var dispute = coreApi.getDispute(req.getTradeId());
var reply = GetDisputeReply.newBuilder()
.setDispute(dispute.toProtoMessage())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleExceptionAsWarning(log, getClass().getName() + ".getDispute", cause, responseObserver);
}
});
});
}
@Override
public void getDisputes(GetDisputesRequest req, StreamObserver<GetDisputesReply> responseObserver) {
try {
var disputes = coreApi.getDisputes();
var disputesProtobuf = disputes.stream()
.map(d -> d.toProtoMessage())
.collect(Collectors.toList());
var reply = GetDisputesReply.newBuilder()
.addAllDisputes(disputesProtobuf)
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
UserThread.execute(() -> {
ThreadUtils.submitToPool(() -> {
try {
var disputes = coreApi.getDisputes();
var disputesProtobuf = disputes.stream()
.map(d -> d.toProtoMessage())
.collect(Collectors.toList());
var reply = GetDisputesReply.newBuilder()
.addAllDisputes(disputesProtobuf)
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
});
});
}
@Override
public void resolveDispute(ResolveDisputeRequest req, StreamObserver<ResolveDisputeReply> responseObserver) {
try {
var winner = ProtoUtil.enumFromProto(DisputeResult.Winner.class, req.getWinner().name());
var reason = ProtoUtil.enumFromProto(DisputeResult.Reason.class, req.getReason().name());
coreApi.resolveDispute(req.getTradeId(), winner, reason, req.getSummaryNotes(), req.getCustomPayoutAmount());
var reply = ResolveDisputeReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
cause.printStackTrace();
exceptionHandler.handleExceptionAsWarning(log, getClass().getName() + ".resolveDispute", cause, responseObserver);
}
UserThread.execute(() -> {
ThreadUtils.submitToPool(() -> {
try {
var winner = ProtoUtil.enumFromProto(DisputeResult.Winner.class, req.getWinner().name());
var reason = ProtoUtil.enumFromProto(DisputeResult.Reason.class, req.getReason().name());
coreApi.resolveDispute(req.getTradeId(), winner, reason, req.getSummaryNotes(), req.getCustomPayoutAmount());
var reply = ResolveDisputeReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
cause.printStackTrace();
exceptionHandler.handleExceptionAsWarning(log, getClass().getName() + ".resolveDispute", cause, responseObserver);
}
});
});
}
@Override
public void sendDisputeChatMessage(SendDisputeChatMessageRequest req,
StreamObserver<SendDisputeChatMessageReply> responseObserver) {
try {
var attachmentsProto = req.getAttachmentsList();
var attachments = attachmentsProto.stream().map(a -> Attachment.fromProto(a))
.collect(Collectors.toList());
coreApi.sendDisputeChatMessage(req.getDisputeId(), req.getMessage(), new ArrayList(attachments));
var reply = SendDisputeChatMessageReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
UserThread.execute(() -> {
ThreadUtils.submitToPool(() -> {
try {
var attachmentsProto = req.getAttachmentsList();
var attachments = attachmentsProto.stream().map(a -> Attachment.fromProto(a))
.collect(Collectors.toList());
coreApi.sendDisputeChatMessage(req.getDisputeId(), req.getMessage(), new ArrayList(attachments));
var reply = SendDisputeChatMessageReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
});
});
}
final ServerInterceptor[] interceptors() {

View file

@ -314,37 +314,39 @@ public class PendingTradesDataModel extends ActivatableDataModel {
///////////////////////////////////////////////////////////////////////////////////////////
private void onListChanged() {
synchronized (tradeManager.getObservableList()) {
UserThread.execute(() -> {
synchronized (tradeManager.getObservableList()) {
// add or remove listener for hidden trades
for (Trade trade : tradeManager.getObservableList()) {
if (isTradeShown(trade)) {
if (hiddenTrades.contains(trade)) {
UserThread.execute(() -> trade.stateProperty().removeListener(hiddenStateChangeListener));
hiddenTrades.remove(trade);
}
} else {
if (!hiddenTrades.contains(trade)) {
UserThread.execute(() -> trade.stateProperty().addListener(hiddenStateChangeListener));
hiddenTrades.add(trade);
// add or remove listener for hidden trades
for (Trade trade : tradeManager.getObservableList()) {
if (isTradeShown(trade)) {
if (hiddenTrades.contains(trade)) {
UserThread.execute(() -> trade.stateProperty().removeListener(hiddenStateChangeListener));
hiddenTrades.remove(trade);
}
} else {
if (!hiddenTrades.contains(trade)) {
UserThread.execute(() -> trade.stateProperty().addListener(hiddenStateChangeListener));
hiddenTrades.add(trade);
}
}
}
}
// add shown trades to list
synchronized (list) {
list.clear();
list.addAll(tradeManager.getObservableList().stream()
.filter(trade -> isTradeShown(trade))
.map(trade -> new PendingTradesListItem(trade, btcFormatter))
.collect(Collectors.toList()));
// add shown trades to list
synchronized (list) {
list.clear();
list.addAll(tradeManager.getObservableList().stream()
.filter(trade -> isTradeShown(trade))
.map(trade -> new PendingTradesListItem(trade, btcFormatter))
.collect(Collectors.toList()));
// we sort by date, earliest first
list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate()));
// we sort by date, earliest first
list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate()));
}
}
}
selectBestItem();
selectBestItem();
});
}
private boolean isTradeShown(Trade trade) {