Skip to content

Commit d01763e

Browse files
authored
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]>
1 parent 20d4f92 commit d01763e

File tree

2 files changed

+75
-22
lines changed

2 files changed

+75
-22
lines changed

models/notification.go

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,9 @@ func (nl NotificationList) getPendingRepoIDs() []int64 {
481481
}
482482

483483
// LoadRepos loads repositories from database
484-
func (nl NotificationList) LoadRepos() (RepositoryList, error) {
484+
func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) {
485485
if len(nl) == 0 {
486-
return RepositoryList{}, nil
486+
return RepositoryList{}, []int{}, nil
487487
}
488488

489489
var repoIDs = nl.getPendingRepoIDs()
@@ -498,15 +498,15 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
498498
In("id", repoIDs[:limit]).
499499
Rows(new(Repository))
500500
if err != nil {
501-
return nil, err
501+
return nil, nil, err
502502
}
503503

504504
for rows.Next() {
505505
var repo Repository
506506
err = rows.Scan(&repo)
507507
if err != nil {
508508
rows.Close()
509-
return nil, err
509+
return nil, nil, err
510510
}
511511

512512
repos[repo.ID] = &repo
@@ -517,14 +517,21 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
517517
repoIDs = repoIDs[limit:]
518518
}
519519

520+
failed := []int{}
521+
520522
var reposList = make(RepositoryList, 0, len(repoIDs))
521-
for _, notification := range nl {
523+
for i, notification := range nl {
522524
if notification.Repository == nil {
523525
notification.Repository = repos[notification.RepoID]
524526
}
527+
if notification.Repository == nil {
528+
log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID)
529+
failed = append(failed, i)
530+
continue
531+
}
525532
var found bool
526533
for _, r := range reposList {
527-
if r.ID == notification.Repository.ID {
534+
if r.ID == notification.RepoID {
528535
found = true
529536
break
530537
}
@@ -533,7 +540,7 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
533540
reposList = append(reposList, notification.Repository)
534541
}
535542
}
536-
return reposList, nil
543+
return reposList, failed, nil
537544
}
538545

539546
func (nl NotificationList) getPendingIssueIDs() []int64 {
@@ -550,9 +557,9 @@ func (nl NotificationList) getPendingIssueIDs() []int64 {
550557
}
551558

552559
// LoadIssues loads issues from database
553-
func (nl NotificationList) LoadIssues() error {
560+
func (nl NotificationList) LoadIssues() ([]int, error) {
554561
if len(nl) == 0 {
555-
return nil
562+
return []int{}, nil
556563
}
557564

558565
var issueIDs = nl.getPendingIssueIDs()
@@ -567,15 +574,15 @@ func (nl NotificationList) LoadIssues() error {
567574
In("id", issueIDs[:limit]).
568575
Rows(new(Issue))
569576
if err != nil {
570-
return err
577+
return nil, err
571578
}
572579

573580
for rows.Next() {
574581
var issue Issue
575582
err = rows.Scan(&issue)
576583
if err != nil {
577584
rows.Close()
578-
return err
585+
return nil, err
579586
}
580587

581588
issues[issue.ID] = &issue
@@ -586,13 +593,38 @@ func (nl NotificationList) LoadIssues() error {
586593
issueIDs = issueIDs[limit:]
587594
}
588595

589-
for _, notification := range nl {
596+
failures := []int{}
597+
598+
for i, notification := range nl {
590599
if notification.Issue == nil {
591600
notification.Issue = issues[notification.IssueID]
601+
if notification.Issue == nil {
602+
log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID)
603+
failures = append(failures, i)
604+
continue
605+
}
592606
notification.Issue.Repo = notification.Repository
593607
}
594608
}
595-
return nil
609+
return failures, nil
610+
}
611+
612+
// Without returns the notification list without the failures
613+
func (nl NotificationList) Without(failures []int) NotificationList {
614+
if failures == nil || len(failures) == 0 {
615+
return nl
616+
}
617+
remaining := make([]*Notification, 0, len(nl))
618+
last := -1
619+
var i int
620+
for _, i = range failures {
621+
remaining = append(remaining, nl[last+1:i]...)
622+
last = i
623+
}
624+
if len(nl) > i {
625+
remaining = append(remaining, nl[i+1:]...)
626+
}
627+
return remaining
596628
}
597629

598630
func (nl NotificationList) getPendingCommentIDs() []int64 {
@@ -609,9 +641,9 @@ func (nl NotificationList) getPendingCommentIDs() []int64 {
609641
}
610642

611643
// LoadComments loads comments from database
612-
func (nl NotificationList) LoadComments() error {
644+
func (nl NotificationList) LoadComments() ([]int, error) {
613645
if len(nl) == 0 {
614-
return nil
646+
return []int{}, nil
615647
}
616648

617649
var commentIDs = nl.getPendingCommentIDs()
@@ -626,15 +658,15 @@ func (nl NotificationList) LoadComments() error {
626658
In("id", commentIDs[:limit]).
627659
Rows(new(Comment))
628660
if err != nil {
629-
return err
661+
return nil, err
630662
}
631663

632664
for rows.Next() {
633665
var comment Comment
634666
err = rows.Scan(&comment)
635667
if err != nil {
636668
rows.Close()
637-
return err
669+
return nil, err
638670
}
639671

640672
comments[comment.ID] = &comment
@@ -645,13 +677,19 @@ func (nl NotificationList) LoadComments() error {
645677
commentIDs = commentIDs[limit:]
646678
}
647679

648-
for _, notification := range nl {
680+
failures := []int{}
681+
for i, notification := range nl {
649682
if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil {
650683
notification.Comment = comments[notification.CommentID]
684+
if notification.Comment == nil {
685+
log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID)
686+
failures = append(failures, i)
687+
continue
688+
}
651689
notification.Comment.Issue = notification.Issue
652690
}
653691
}
654-
return nil
692+
return failures, nil
655693
}
656694

657695
// 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
@@ -81,24 +81,39 @@ func Notifications(c *context.Context) {
8181
return
8282
}
8383

84-
repos, err := notifications.LoadRepos()
84+
failCount := 0
85+
86+
repos, failures, err := notifications.LoadRepos()
8587
if err != nil {
8688
c.ServerError("LoadRepos", err)
8789
return
8890
}
91+
notifications = notifications.Without(failures)
8992
if err := repos.LoadAttributes(); err != nil {
9093
c.ServerError("LoadAttributes", err)
9194
return
9295
}
96+
failCount += len(failures)
9397

94-
if err := notifications.LoadIssues(); err != nil {
98+
failures, err = notifications.LoadIssues()
99+
if err != nil {
95100
c.ServerError("LoadIssues", err)
96101
return
97102
}
98-
if err := notifications.LoadComments(); err != nil {
103+
notifications = notifications.Without(failures)
104+
failCount += len(failures)
105+
106+
failures, err = notifications.LoadComments()
107+
if err != nil {
99108
c.ServerError("LoadComments", err)
100109
return
101110
}
111+
notifications = notifications.Without(failures)
112+
failCount += len(failures)
113+
114+
if failCount > 0 {
115+
c.Flash.Error(fmt.Sprintf("ERROR: %d notifications were removed due to missing parts - check the logs", failCount))
116+
}
102117

103118
title := c.Tr("notifications")
104119
if status == models.NotificationStatusUnread && total > 0 {

0 commit comments

Comments
 (0)