mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-11-16 15:58:08 +00:00
fix concurrent modification exception in disputes list
This commit is contained in:
parent
d9a3feba8d
commit
3dfaa2fc52
7 changed files with 80 additions and 58 deletions
|
@ -275,10 +275,12 @@ public class CoreDisputesService {
|
||||||
disputeResult.summaryNotesProperty().get()
|
disputeResult.summaryNotesProperty().get()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (reason == DisputeResult.Reason.OPTION_TRADE &&
|
synchronized (dispute.getChatMessages()) {
|
||||||
|
if (reason == DisputeResult.Reason.OPTION_TRADE &&
|
||||||
dispute.getChatMessages().size() > 1 &&
|
dispute.getChatMessages().size() > 1 &&
|
||||||
dispute.getChatMessages().get(1).isSystemMessage()) {
|
dispute.getChatMessages().get(1).isSystemMessage()) {
|
||||||
textToSign += "\n" + dispute.getChatMessages().get(1).getMessage() + "\n";
|
textToSign += "\n" + dispute.getChatMessages().get(1).getMessage() + "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign);
|
String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign);
|
||||||
|
|
|
@ -114,16 +114,18 @@ public class DisputeMsgEvents {
|
||||||
// We check at every new message if it might be a message sent after the dispute had been closed. If that is the
|
// We check at every new message if it might be a message sent after the dispute had been closed. If that is the
|
||||||
// case we revert the isClosed flag so that the UI can reopen the dispute and indicate that a new dispute
|
// case we revert the isClosed flag so that the UI can reopen the dispute and indicate that a new dispute
|
||||||
// message arrived.
|
// message arrived.
|
||||||
ObservableList<ChatMessage> chatMessages = dispute.getChatMessages();
|
synchronized (dispute.getChatMessages()) {
|
||||||
// If last message is not a result message we re-open as we might have received a new message from the
|
ObservableList<ChatMessage> chatMessages = dispute.getChatMessages();
|
||||||
// trader/mediator/arbitrator who has reopened the case
|
// If last message is not a result message we re-open as we might have received a new message from the
|
||||||
if (dispute.isClosed() && !chatMessages.isEmpty() && !chatMessages.get(chatMessages.size() - 1).isResultMessage(dispute)) {
|
// trader/mediator/arbitrator who has reopened the case
|
||||||
dispute.reOpen();
|
if (dispute.isClosed() && !chatMessages.isEmpty() && !chatMessages.get(chatMessages.size() - 1).isResultMessage(dispute)) {
|
||||||
if (dispute.getSupportType() == SupportType.MEDIATION) {
|
dispute.reOpen();
|
||||||
mediationManager.requestPersistence();
|
if (dispute.getSupportType() == SupportType.MEDIATION) {
|
||||||
} else if (dispute.getSupportType() == SupportType.REFUND) {
|
mediationManager.requestPersistence();
|
||||||
refundManager.requestPersistence();
|
} else if (dispute.getSupportType() == SupportType.REFUND) {
|
||||||
|
refundManager.requestPersistence();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,17 +192,19 @@ public abstract class SupportManager {
|
||||||
if (ackMessage.getSourceMsgClassName().equals(ChatMessage.class.getSimpleName())) {
|
if (ackMessage.getSourceMsgClassName().equals(ChatMessage.class.getSimpleName())) {
|
||||||
Trade trade = tradeManager.getTrade(ackMessage.getSourceId());
|
Trade trade = tradeManager.getTrade(ackMessage.getSourceId());
|
||||||
for (Dispute dispute : trade.getDisputes()) {
|
for (Dispute dispute : trade.getDisputes()) {
|
||||||
for (ChatMessage chatMessage : dispute.getChatMessages()) {
|
synchronized (dispute.getChatMessages()) {
|
||||||
if (chatMessage.getUid().equals(ackMessage.getSourceUid())) {
|
for (ChatMessage chatMessage : dispute.getChatMessages()) {
|
||||||
if (trade.getDisputeState() == Trade.DisputeState.DISPUTE_REQUESTED) {
|
if (chatMessage.getUid().equals(ackMessage.getSourceUid())) {
|
||||||
if (dispute.isClosed()) dispute.reOpen();
|
if (trade.getDisputeState() == Trade.DisputeState.DISPUTE_REQUESTED) {
|
||||||
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
|
if (dispute.isClosed()) dispute.reOpen();
|
||||||
} else if (dispute.isClosed()) {
|
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
|
||||||
trade.pollWalletNormallyForMs(30000); // sync to check for payout
|
} else if (dispute.isClosed()) {
|
||||||
|
trade.pollWalletNormallyForMs(30000); // sync to check for payout
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("Received AckMessage with error state for {} with tradeId={}, sender={}, errorMessage={}",
|
log.warn("Received AckMessage with error state for {} with tradeId={}, sender={}, errorMessage={}",
|
||||||
|
@ -212,12 +214,14 @@ public abstract class SupportManager {
|
||||||
if (ackMessage.getSourceMsgClassName().equals(ChatMessage.class.getSimpleName())) {
|
if (ackMessage.getSourceMsgClassName().equals(ChatMessage.class.getSimpleName())) {
|
||||||
Trade trade = tradeManager.getTrade(ackMessage.getSourceId());
|
Trade trade = tradeManager.getTrade(ackMessage.getSourceId());
|
||||||
for (Dispute dispute : trade.getDisputes()) {
|
for (Dispute dispute : trade.getDisputes()) {
|
||||||
for (ChatMessage chatMessage : dispute.getChatMessages()) {
|
synchronized (dispute.getChatMessages()) {
|
||||||
if (chatMessage.getUid().equals(ackMessage.getSourceUid())) {
|
for (ChatMessage chatMessage : dispute.getChatMessages()) {
|
||||||
if (trade.getDisputeState().isCloseRequested()) {
|
if (chatMessage.getUid().equals(ackMessage.getSourceUid())) {
|
||||||
log.warn("DisputeCloseMessage was nacked. We close the dispute now. tradeId={}, nack sender={}", trade.getId(), ackMessage.getSenderNodeAddress());
|
if (trade.getDisputeState().isCloseRequested()) {
|
||||||
dispute.setIsClosed();
|
log.warn("DisputeCloseMessage was nacked. We close the dispute now. tradeId={}, nack sender={}", trade.getId(), ackMessage.getSenderNodeAddress());
|
||||||
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
|
dispute.setIsClosed();
|
||||||
|
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,10 +353,12 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void addAndPersistChatMessage(ChatMessage chatMessage) {
|
public void addAndPersistChatMessage(ChatMessage chatMessage) {
|
||||||
if (!chatMessages.contains(chatMessage)) {
|
synchronized (chatMessages) {
|
||||||
chatMessages.add(chatMessage);
|
if (!chatMessages.contains(chatMessage)) {
|
||||||
} else {
|
chatMessages.add(chatMessage);
|
||||||
log.error("disputeDirectMessage already exists");
|
} else {
|
||||||
|
log.error("disputeDirectMessage already exists");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,13 +367,15 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeAllChatMessages() {
|
public boolean removeAllChatMessages() {
|
||||||
if (chatMessages.size() > 1) {
|
synchronized (chatMessages) {
|
||||||
// removes all chat except the initial guidelines message.
|
if (chatMessages.size() > 1) {
|
||||||
String firstMessageUid = chatMessages.get(0).getUid();
|
// removes all chat except the initial guidelines message.
|
||||||
chatMessages.removeIf((msg) -> !msg.getUid().equals(firstMessageUid));
|
String firstMessageUid = chatMessages.get(0).getUid();
|
||||||
return true;
|
chatMessages.removeIf((msg) -> !msg.getUid().equals(firstMessageUid));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void maybeClearSensitiveData() {
|
public void maybeClearSensitiveData() {
|
||||||
|
|
|
@ -90,12 +90,14 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void cleanupDisputes(@Nullable Consumer<String> closedDisputeHandler) {
|
public void cleanupDisputes(@Nullable Consumer<String> closedDisputeHandler) {
|
||||||
disputeList.stream().forEach(dispute -> {
|
synchronized (disputeList.getObservableList()) {
|
||||||
String tradeId = dispute.getTradeId();
|
disputeList.stream().forEach(dispute -> {
|
||||||
if (dispute.isClosed() && closedDisputeHandler != null) {
|
String tradeId = dispute.getTradeId();
|
||||||
closedDisputeHandler.accept(tradeId);
|
if (dispute.isClosed() && closedDisputeHandler != null) {
|
||||||
}
|
closedDisputeHandler.accept(tradeId);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,7 +132,9 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
ObservableList<Dispute> getObservableList() {
|
ObservableList<Dispute> getObservableList() {
|
||||||
return disputeList.getObservableList();
|
synchronized (disputeList.getObservableList()) {
|
||||||
|
return disputeList.getObservableList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -151,10 +155,12 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
|
||||||
isAlerting -> {
|
isAlerting -> {
|
||||||
// We get the event before the list gets updated, so we execute on next frame
|
// We get the event before the list gets updated, so we execute on next frame
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
int numAlerts = (int) disputeList.getList().stream()
|
synchronized (disputeList.getObservableList()) {
|
||||||
.mapToLong(x -> x.getBadgeCountProperty().getValue())
|
int numAlerts = (int) disputeList.getList().stream()
|
||||||
.sum();
|
.mapToLong(x -> x.getBadgeCountProperty().getValue())
|
||||||
numOpenDisputes.set(numAlerts);
|
.sum();
|
||||||
|
numOpenDisputes.set(numAlerts);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
disputedTradeIds.add(dispute.getTradeId());
|
disputedTradeIds.add(dispute.getTradeId());
|
||||||
|
|
|
@ -187,10 +187,12 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ChatMessage> getAllChatMessages(String tradeId) {
|
public List<ChatMessage> getAllChatMessages(String tradeId) {
|
||||||
return getDisputeList().stream()
|
synchronized (getDisputeList().getObservableList()) {
|
||||||
.filter(dispute -> dispute.getTradeId().equals(tradeId))
|
return getDisputeList().stream()
|
||||||
.flatMap(dispute -> dispute.getChatMessages().stream())
|
.filter(dispute -> dispute.getTradeId().equals(tradeId))
|
||||||
.collect(Collectors.toList());
|
.flatMap(dispute -> dispute.getChatMessages().stream())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -239,7 +241,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableList<Dispute> getDisputesAsObservableList() {
|
public ObservableList<Dispute> getDisputesAsObservableList() {
|
||||||
synchronized(disputeListService.getDisputeList()) {
|
synchronized(disputeListService.getDisputeList().getObservableList()) {
|
||||||
return disputeListService.getObservableList();
|
return disputeListService.getObservableList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,7 +251,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
}
|
}
|
||||||
|
|
||||||
protected T getDisputeList() {
|
protected T getDisputeList() {
|
||||||
synchronized(disputeListService.getDisputeList()) {
|
synchronized(disputeListService.getDisputeList().getObservableList()) {
|
||||||
return disputeListService.getDisputeList();
|
return disputeListService.getDisputeList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,7 +356,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (disputeList) {
|
synchronized (disputeList.getObservableList()) {
|
||||||
if (disputeList.contains(dispute)) {
|
if (disputeList.contains(dispute)) {
|
||||||
String msg = "We got a dispute msg that we have already stored. TradeId = " + dispute.getTradeId() + ", DisputeId = " + dispute.getId();
|
String msg = "We got a dispute msg that we have already stored. TradeId = " + dispute.getTradeId() + ", DisputeId = " + dispute.getId();
|
||||||
log.warn(msg);
|
log.warn(msg);
|
||||||
|
|
|
@ -287,10 +287,12 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
|
|
||||||
// set dispute state
|
// set dispute state
|
||||||
cleanupRetryMap(uid);
|
cleanupRetryMap(uid);
|
||||||
if (!dispute.getChatMessages().contains(chatMessage)) {
|
synchronized (dispute.getChatMessages()) {
|
||||||
dispute.addAndPersistChatMessage(chatMessage);
|
if (!dispute.getChatMessages().contains(chatMessage)) {
|
||||||
} else {
|
dispute.addAndPersistChatMessage(chatMessage);
|
||||||
log.warn("We got a dispute mail msg that we have already stored. TradeId = " + chatMessage.getTradeId());
|
} else {
|
||||||
|
log.warn("We got a dispute mail msg that we have already stored. TradeId = " + chatMessage.getTradeId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dispute.setIsClosed();
|
dispute.setIsClosed();
|
||||||
if (dispute.disputeResultProperty().get() != null) {
|
if (dispute.disputeResultProperty().get() != null) {
|
||||||
|
|
Loading…
Reference in a new issue