Skip to content

Commit 596eebb

Browse files
zeripathlafriks
andauthored
Protect against NPEs in notifications list (#10879) (#10883)
* Protect against NPEs in notifications list (#10879) Unfortunately there appears to be potential race with notifications being set before the associated issue has been committed. This PR adds protection in to the notifications list to log any failures and remove these notifications from the display. References #10815 - and prevents the panic but does not completely fix this. Signed-off-by: Andrew Thornton <[email protected]> * add log import * Update models/notification.go Co-Authored-By: Lauris BH <[email protected]> Co-authored-by: Lauris BH <[email protected]>
1 parent 1d5d745 commit 596eebb

File tree

2 files changed

+76
-22
lines changed

2 files changed

+76
-22
lines changed

models/notification.go

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package models
77
import (
88
"fmt"
99

10+
"code.gitea.io/gitea/modules/log"
1011
"code.gitea.io/gitea/modules/timeutil"
1112
)
1213

@@ -281,9 +282,9 @@ func (nl NotificationList) getPendingRepoIDs() []int64 {
281282
}
282283

283284
// LoadRepos loads repositories from database
284-
func (nl NotificationList) LoadRepos() (RepositoryList, error) {
285+
func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) {
285286
if len(nl) == 0 {
286-
return RepositoryList{}, nil
287+
return RepositoryList{}, []int{}, nil
287288
}
288289

289290
var repoIDs = nl.getPendingRepoIDs()
@@ -298,15 +299,15 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
298299
In("id", repoIDs[:limit]).
299300
Rows(new(Repository))
300301
if err != nil {
301-
return nil, err
302+
return nil, nil, err
302303
}
303304

304305
for rows.Next() {
305306
var repo Repository
306307
err = rows.Scan(&repo)
307308
if err != nil {
308309
rows.Close()
309-
return nil, err
310+
return nil, nil, err
310311
}
311312

312313
repos[repo.ID] = &repo
@@ -317,14 +318,21 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
317318
repoIDs = repoIDs[limit:]
318319
}
319320

321+
failed := []int{}
322+
320323
var reposList = make(RepositoryList, 0, len(repoIDs))
321-
for _, notification := range nl {
324+
for i, notification := range nl {
322325
if notification.Repository == nil {
323326
notification.Repository = repos[notification.RepoID]
324327
}
328+
if notification.Repository == nil {
329+
log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID)
330+
failed = append(failed, i)
331+
continue
332+
}
325333
var found bool
326334
for _, r := range reposList {
327-
if r.ID == notification.Repository.ID {
335+
if r.ID == notification.RepoID {
328336
found = true
329337
break
330338
}
@@ -333,7 +341,7 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
333341
reposList = append(reposList, notification.Repository)
334342
}
335343
}
336-
return reposList, nil
344+
return reposList, failed, nil
337345
}
338346

339347
func (nl NotificationList) getPendingIssueIDs() []int64 {
@@ -350,9 +358,9 @@ func (nl NotificationList) getPendingIssueIDs() []int64 {
350358
}
351359

352360
// LoadIssues loads issues from database
353-
func (nl NotificationList) LoadIssues() error {
361+
func (nl NotificationList) LoadIssues() ([]int, error) {
354362
if len(nl) == 0 {
355-
return nil
363+
return []int{}, nil
356364
}
357365

358366
var issueIDs = nl.getPendingIssueIDs()
@@ -367,15 +375,15 @@ func (nl NotificationList) LoadIssues() error {
367375
In("id", issueIDs[:limit]).
368376
Rows(new(Issue))
369377
if err != nil {
370-
return err
378+
return nil, err
371379
}
372380

373381
for rows.Next() {
374382
var issue Issue
375383
err = rows.Scan(&issue)
376384
if err != nil {
377385
rows.Close()
378-
return err
386+
return nil, err
379387
}
380388

381389
issues[issue.ID] = &issue
@@ -386,13 +394,38 @@ func (nl NotificationList) LoadIssues() error {
386394
issueIDs = issueIDs[limit:]
387395
}
388396

389-
for _, notification := range nl {
397+
failures := []int{}
398+
399+
for i, notification := range nl {
390400
if notification.Issue == nil {
391401
notification.Issue = issues[notification.IssueID]
402+
if notification.Issue == nil {
403+
log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID)
404+
failures = append(failures, i)
405+
continue
406+
}
392407
notification.Issue.Repo = notification.Repository
393408
}
394409
}
395-
return nil
410+
return failures, nil
411+
}
412+
413+
// Without returns the notification list without the failures
414+
func (nl NotificationList) Without(failures []int) NotificationList {
415+
if len(failures) == 0 {
416+
return nl
417+
}
418+
remaining := make([]*Notification, 0, len(nl))
419+
last := -1
420+
var i int
421+
for _, i = range failures {
422+
remaining = append(remaining, nl[last+1:i]...)
423+
last = i
424+
}
425+
if len(nl) > i {
426+
remaining = append(remaining, nl[i+1:]...)
427+
}
428+
return remaining
396429
}
397430

398431
func (nl NotificationList) getPendingCommentIDs() []int64 {
@@ -409,9 +442,9 @@ func (nl NotificationList) getPendingCommentIDs() []int64 {
409442
}
410443

411444
// LoadComments loads comments from database
412-
func (nl NotificationList) LoadComments() error {
445+
func (nl NotificationList) LoadComments() ([]int, error) {
413446
if len(nl) == 0 {
414-
return nil
447+
return []int{}, nil
415448
}
416449

417450
var commentIDs = nl.getPendingCommentIDs()
@@ -426,15 +459,15 @@ func (nl NotificationList) LoadComments() error {
426459
In("id", commentIDs[:limit]).
427460
Rows(new(Comment))
428461
if err != nil {
429-
return err
462+
return nil, err
430463
}
431464

432465
for rows.Next() {
433466
var comment Comment
434467
err = rows.Scan(&comment)
435468
if err != nil {
436469
rows.Close()
437-
return err
470+
return nil, err
438471
}
439472

440473
comments[comment.ID] = &comment
@@ -445,13 +478,19 @@ func (nl NotificationList) LoadComments() error {
445478
commentIDs = commentIDs[limit:]
446479
}
447480

448-
for _, notification := range nl {
481+
failures := []int{}
482+
for i, notification := range nl {
449483
if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil {
450484
notification.Comment = comments[notification.CommentID]
485+
if notification.Comment == nil {
486+
log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID)
487+
failures = append(failures, i)
488+
continue
489+
}
451490
notification.Comment.Issue = notification.Issue
452491
}
453492
}
454-
return nil
493+
return failures, nil
455494
}
456495

457496
// GetNotificationCount returns the notification count for user

routers/user/notification.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,39 @@ func Notifications(c *context.Context) {
6868
return
6969
}
7070

71-
repos, err := notifications.LoadRepos()
71+
failCount := 0
72+
73+
repos, failures, err := notifications.LoadRepos()
7274
if err != nil {
7375
c.ServerError("LoadRepos", err)
7476
return
7577
}
78+
notifications = notifications.Without(failures)
7679
if err := repos.LoadAttributes(); err != nil {
7780
c.ServerError("LoadAttributes", err)
7881
return
7982
}
83+
failCount += len(failures)
8084

81-
if err := notifications.LoadIssues(); err != nil {
85+
failures, err = notifications.LoadIssues()
86+
if err != nil {
8287
c.ServerError("LoadIssues", err)
8388
return
8489
}
85-
if err := notifications.LoadComments(); err != nil {
90+
notifications = notifications.Without(failures)
91+
failCount += len(failures)
92+
93+
failures, err = notifications.LoadComments()
94+
if err != nil {
8695
c.ServerError("LoadComments", err)
8796
return
8897
}
98+
notifications = notifications.Without(failures)
99+
failCount += len(failures)
100+
101+
if failCount > 0 {
102+
c.Flash.Error(fmt.Sprintf("ERROR: %d notifications were removed due to missing parts - check the logs", failCount))
103+
}
89104

90105
total, err := models.GetNotificationCount(c.User, status)
91106
if err != nil {

0 commit comments

Comments
 (0)