Skip to content

Commit 05d73da

Browse files
CopilotMistium
andcommitted
Fix additional deadlocks in handlers_devfund, handlers_friends, handlers_items, and handlers_keys
- Fix deadlocks in escrowTransfer and escrowRelease (handlers_devfund.go) - Fix deadlock in rejectFriendRequest (handlers_friends.go) - Fix deadlock in buyItem (handlers_items.go) - Fix deadlock in checkSubscriptions (handlers_keys.go) - All fixes use direct access methods while holding usersMutex to avoid nested locking Co-authored-by: Mistium <92952823+Mistium@users.noreply.github.com>
1 parent 99e95d9 commit 05d73da

4 files changed

Lines changed: 66 additions & 25 deletions

File tree

handlers_devfund.go

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,22 @@ func escrowTransfer(c *gin.Context) {
5353
}
5454
}
5555
if fromUser == nil {
56+
usersMutex.Unlock()
5657
c.JSON(404, gin.H{"error": "Sender user not found"})
5758
return
5859
}
5960

6061
// Check sender balance
6162
fromCurrency := fromUser.GetCredits()
6263
if fromCurrency == 0 {
64+
usersMutex.Unlock()
6365
c.JSON(400, gin.H{"error": "Sender user has no currency"})
6466
return
6567
}
6668
fromCurrency = roundVal(fromCurrency)
6769

6870
if fromCurrency < nAmount {
71+
usersMutex.Unlock()
6972
c.JSON(400, gin.H{"error": "Insufficient funds", "required": nAmount, "available": fromCurrency})
7073
return
7174
}
@@ -75,12 +78,9 @@ func escrowTransfer(c *gin.Context) {
7578
if newBal < 0 { // guard against tiny floating error
7679
newBal = 0
7780
}
78-
usersMutex.Unlock()
79-
80-
fromUser.SetBalance(newBal)
81-
82-
usersMutex.Lock()
83-
defer usersMutex.Unlock()
81+
82+
// Update balance directly while holding lock
83+
setUserKeyDirect(fromUser, "sys.currency", roundVal(newBal))
8484

8585
// Add escrow transaction to sender
8686
now := time.Now().UnixMilli()
@@ -92,15 +92,27 @@ func escrowTransfer(c *gin.Context) {
9292
note = note[:50]
9393
}
9494

95-
fromUser.addTransaction(map[string]any{
95+
// Add transaction directly while holding lock
96+
txs := getObjectSlice(*fromUser, "sys.transactions")
97+
benefits := fromUser.GetSubscriptionBenefits()
98+
99+
tx := map[string]any{
96100
"note": note,
97101
"user": "devfund-escrow",
98102
"time": now,
99103
"amount": nAmount,
100104
"type": "escrow_out",
101105
"petition_id": req.PetitionID,
102106
"new_total": newBal,
103-
})
107+
}
108+
109+
txs = append([]map[string]any{tx}, txs...)
110+
if len(txs) > benefits.Max_Transaction_History {
111+
txs = txs[:benefits.Max_Transaction_History]
112+
}
113+
setUserKeyDirect(fromUser, "sys.transactions", txs)
114+
115+
usersMutex.Unlock()
104116

105117
go saveUsers()
106118

@@ -164,24 +176,20 @@ func escrowRelease(c *gin.Context) {
164176
}
165177
}
166178
if toUser == nil {
179+
usersMutex.Unlock()
167180
c.JSON(404, gin.H{"error": "Recipient user not found"})
168181
return
169182
}
170183

171-
usersMutex.Unlock()
172184
// Get recipient balance
173185
toCurrency := toUser.GetCredits()
174186
if toCurrency == 0 {
175-
toUser.SetBalance(float64(0))
176187
toCurrency = float64(0)
177188
}
178189

179190
// Add credits to recipient
180191
newBal := roundVal(toCurrency + nAmount)
181-
toUser.SetBalance(newBal)
182-
183-
usersMutex.Lock()
184-
defer usersMutex.Unlock()
192+
setUserKeyDirect(toUser, "sys.currency", newBal)
185193

186194
// Add transaction to recipient
187195
now := time.Now().UnixMilli()
@@ -193,16 +201,27 @@ func escrowRelease(c *gin.Context) {
193201
note = note[:50]
194202
}
195203

196-
// Helper to add transaction
197-
toUser.addTransaction(map[string]any{
204+
// Add transaction directly while holding lock
205+
txs := getObjectSlice(*toUser, "sys.transactions")
206+
benefits := toUser.GetSubscriptionBenefits()
207+
208+
tx := map[string]any{
198209
"note": note,
199210
"user": "devfund-escrow",
200211
"time": now,
201212
"amount": nAmount,
202213
"type": "escrow_in",
203214
"petition_id": req.PetitionID,
204215
"new_total": newBal,
205-
})
216+
}
217+
218+
txs = append([]map[string]any{tx}, txs...)
219+
if len(txs) > benefits.Max_Transaction_History {
220+
txs = txs[:benefits.Max_Transaction_History]
221+
}
222+
setUserKeyDirect(toUser, "sys.transactions", txs)
223+
224+
usersMutex.Unlock()
206225

207226
go saveUsers()
208227

handlers_friends.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ func rejectFriendRequest(c *gin.Context) {
152152

153153
usersMutex.Lock()
154154

155-
currentRequests := getStringSlice(*current, "sys.requests")
155+
// Use direct access since we hold usersMutex
156+
currentRequests := getStringSliceDirect(*current, "sys.requests")
156157
found := false
157158
newRequests := make([]string, 0, len(currentRequests))
158159
for _, r := range currentRequests {

handlers_items.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ func buyItem(c *gin.Context) {
159159
c.JSON(404, gin.H{"error": "User not found"})
160160
return
161161
}
162-
users[userIndex].SetBalance(userCurrency - float64(targetItem.Price))
162+
// Use direct access since we hold usersMutex
163+
setUserKeyDirect(&users[userIndex], "sys.currency", roundVal(userCurrency-float64(targetItem.Price)))
163164
go saveUsers()
164165

165166
// Notify both users

handlers_keys.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -640,31 +640,51 @@ func checkSubscriptions() {
640640
}
641641
currencyFloat -= float64(userData.Price)
642642
usersMutex.Lock()
643-
purchaser.SetBalance(currencyFloat)
644-
purchaser.addTransaction(map[string]any{
643+
// Use direct access since we hold usersMutex
644+
setUserKeyDirect(&users[userIndex], "sys.currency", roundVal(currencyFloat))
645+
646+
// Add transaction directly
647+
txs := getObjectSlice(users[userIndex], "sys.transactions")
648+
benefits := users[userIndex].GetSubscriptionBenefits()
649+
tx := map[string]any{
645650
"note": "key purchase",
646651
"key_id": key.Key,
647652
"key_name": key.Name,
648653
"user": key.Creator,
649654
"amount": float64(userData.Price),
650655
"type": "key_buy",
651656
"new_total": currencyFloat,
652-
})
657+
}
658+
txs = append([]map[string]any{tx}, txs...)
659+
if len(txs) > benefits.Max_Transaction_History {
660+
txs = txs[:benefits.Max_Transaction_History]
661+
}
662+
setUserKeyDirect(&users[userIndex], "sys.transactions", txs)
653663

654664
// 10% tax on purchase
655665
value := float64(userData.Price) * 0.9
656666
owner := users[ownerIndex]
657667
newBal := owner.GetCredits() + value
658-
owner.SetBalance(newBal)
659-
owner.addTransaction(map[string]any{
668+
setUserKeyDirect(&users[ownerIndex], "sys.currency", roundVal(newBal))
669+
670+
// Add transaction for owner
671+
ownerTxs := getObjectSlice(owner, "sys.transactions")
672+
ownerBenefits := owner.GetSubscriptionBenefits()
673+
ownerTx := map[string]any{
660674
"note": "key purchase",
661675
"key_id": key.Key,
662676
"key_name": key.Name,
663677
"user": username,
664678
"amount": float64(userData.Price),
665679
"type": "key_sale",
666680
"new_total": newBal,
667-
})
681+
}
682+
ownerTxs = append([]map[string]any{ownerTx}, ownerTxs...)
683+
if len(ownerTxs) > ownerBenefits.Max_Transaction_History {
684+
ownerTxs = ownerTxs[:ownerBenefits.Max_Transaction_History]
685+
}
686+
setUserKeyDirect(&users[ownerIndex], "sys.transactions", ownerTxs)
687+
668688
usersMutex.Unlock()
669689
go saveUsers()
670690

0 commit comments

Comments
 (0)