Skip to content

Commit 2ff420f

Browse files
committed
Protect against NPEs in notifications list (go-gitea#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 go-gitea#10815 - and prevents the panic but does not completely fix this. Signed-off-by: Andrew Thornton <[email protected]>
1 parent 1d5d745 commit 2ff420f

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
@@ -281,9 +281,9 @@ func (nl NotificationList) getPendingRepoIDs() []int64 {
281281
}
282282

283283
// LoadRepos loads repositories from database
284-
func (nl NotificationList) LoadRepos() (RepositoryList, error) {
284+
func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) {
285285
if len(nl) == 0 {
286-
return RepositoryList{}, nil
286+
return RepositoryList{}, []int{}, nil
287287
}
288288

289289
var repoIDs = nl.getPendingRepoIDs()
@@ -298,15 +298,15 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
298298
In("id", repoIDs[:limit]).
299299
Rows(new(Repository))
300300
if err != nil {
301-
return nil, err
301+
return nil, nil, err
302302
}
303303

304304
for rows.Next() {
305305
var repo Repository
306306
err = rows.Scan(&repo)
307307
if err != nil {
308308
rows.Close()
309-
return nil, err
309+
return nil, nil, err
310310
}
311311

312312
repos[repo.ID] = &repo
@@ -317,14 +317,21 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
317317
repoIDs = repoIDs[limit:]
318318
}
319319

320+
failed := []int{}
321+
320322
var reposList = make(RepositoryList, 0, len(repoIDs))
321-
for _, notification := range nl {
323+
for i, notification := range nl {
322324
if notification.Repository == nil {
323325
notification.Repository = repos[notification.RepoID]
324326
}
327+
if notification.Repository == nil {
328+
log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID)
329+
failed = append(failed, i)
330+
continue
331+
}
325332
var found bool
326333
for _, r := range reposList {
327-
if r.ID == notification.Repository.ID {
334+
if r.ID == notification.RepoID {
328335
found = true
329336
break
330337
}
@@ -333,7 +340,7 @@ func (nl NotificationList) LoadRepos() (RepositoryList, error) {
333340
reposList = append(reposList, notification.Repository)
334341
}
335342
}
336-
return reposList, nil
343+
return reposList, failed, nil
337344
}
338345

339346
func (nl NotificationList) getPendingIssueIDs() []int64 {
@@ -350,9 +357,9 @@ func (nl NotificationList) getPendingIssueIDs() []int64 {
350357
}
351358

352359
// LoadIssues loads issues from database
353-
func (nl NotificationList) LoadIssues() error {
360+
func (nl NotificationList) LoadIssues() ([]int, error) {
354361
if len(nl) == 0 {
355-
return nil
362+
return []int{}, nil
356363
}
357364

358365
var issueIDs = nl.getPendingIssueIDs()
@@ -367,15 +374,15 @@ func (nl NotificationList) LoadIssues() error {
367374
In("id", issueIDs[:limit]).
368375
Rows(new(Issue))
369376
if err != nil {
370-
return err
377+
return nil, err
371378
}
372379

373380
for rows.Next() {
374381
var issue Issue
375382
err = rows.Scan(&issue)
376383
if err != nil {
377384
rows.Close()
378-
return err
385+
return nil, err
379386
}
380387

381388
issues[issue.ID] = &issue
@@ -386,13 +393,38 @@ func (nl NotificationList) LoadIssues() error {
386393
issueIDs = issueIDs[limit:]
387394
}
388395

389-
for _, notification := range nl {
396+
failures := []int{}
397+
398+
for i, notification := range nl {
390399
if notification.Issue == nil {
391400
notification.Issue = issues[notification.IssueID]
401+
if notification.Issue == nil {
402+
log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID)
403+
failures = append(failures, i)
404+
continue
405+
}
392406
notification.Issue.Repo = notification.Repository
393407
}
394408
}
395-
return nil
409+
return failures, nil
410+
}
411+
412+
// Without returns the notification list without the failures
413+
func (nl NotificationList) Without(failures []int) NotificationList {
414+
if failures == nil || len(failures) == 0 {
415+
return nl
416+
}
417+
remaining := make([]*Notification, 0, len(nl))
418+
last := -1
419+
var i int
420+
for _, i = range failures {
421+
remaining = append(remaining, nl[last+1:i]...)
422+
last = i
423+
}
424+
if len(nl) > i {
425+
remaining = append(remaining, nl[i+1:]...)
426+
}
427+
return remaining
396428
}
397429

398430
func (nl NotificationList) getPendingCommentIDs() []int64 {
@@ -409,9 +441,9 @@ func (nl NotificationList) getPendingCommentIDs() []int64 {
409441
}
410442

411443
// LoadComments loads comments from database
412-
func (nl NotificationList) LoadComments() error {
444+
func (nl NotificationList) LoadComments() ([]int, error) {
413445
if len(nl) == 0 {
414-
return nil
446+
return []int{}, nil
415447
}
416448

417449
var commentIDs = nl.getPendingCommentIDs()
@@ -426,15 +458,15 @@ func (nl NotificationList) LoadComments() error {
426458
In("id", commentIDs[:limit]).
427459
Rows(new(Comment))
428460
if err != nil {
429-
return err
461+
return nil, err
430462
}
431463

432464
for rows.Next() {
433465
var comment Comment
434466
err = rows.Scan(&comment)
435467
if err != nil {
436468
rows.Close()
437-
return err
469+
return nil, err
438470
}
439471

440472
comments[comment.ID] = &comment
@@ -445,13 +477,19 @@ func (nl NotificationList) LoadComments() error {
445477
commentIDs = commentIDs[limit:]
446478
}
447479

448-
for _, notification := range nl {
480+
failures := []int{}
481+
for i, notification := range nl {
449482
if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil {
450483
notification.Comment = comments[notification.CommentID]
484+
if notification.Comment == nil {
485+
log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID)
486+
failures = append(failures, i)
487+
continue
488+
}
451489
notification.Comment.Issue = notification.Issue
452490
}
453491
}
454-
return nil
492+
return failures, nil
455493
}
456494

457495
// 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)