detached 91d6720

fix(snapshot): surface cursor close errors

commit
91d6720c5323213216fde21ea9b8d050eb5b19b4
author
brice · · 2026-07-21 21:29 UTC
parent
11815fb
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +14 -3
M internal/adapters/snapshot/maintenance.go +14 -3
@@ -260,12 +260,23 @@ func (r *Repository) readMaintenanceDir(dir string, n int, purpose string) ([]ma
260260 // readMaintenanceDirent uses the Linux getdents seek cookie from each record.
261261 // syscall.Dirent's Off field is the d_off member of linux_dirent64 and
262262 // syscall.Seek is lseek(2), as defined by Go's syscall Linux sources.
263-func readMaintenanceDirent(dir string, limit int, cursor *maintenanceCursor) ([]maintenanceDirEntry, bool, error) {
263+func readMaintenanceDirent(dir string, limit int, cursor *maintenanceCursor) (entries []maintenanceDirEntry, done bool, err error) {
264264 file, err := os.Open(dir)
265265 if err != nil {
266266 return nil, false, err
267267 }
268- defer file.Close()
268+ defer func() {
269+ if closeErr := file.Close(); closeErr != nil {
270+ closeErr = fmt.Errorf("close maintenance directory %q: %w", dir, closeErr)
271+ if err != nil {
272+ err = errors.Join(err, closeErr)
273+ } else {
274+ entries = nil
275+ done = false
276+ err = closeErr
277+ }
278+ }
279+ }()
269280 fd := int(file.Fd())
270281 if cursor.offset != 0 {
271282 if _, err := syscall.Seek(fd, cursor.offset, io.SeekStart); err != nil {
@@ -273,7 +284,7 @@ func readMaintenanceDirent(dir string, limit int, cursor *maintenanceCursor) ([]
273284 }
274285 }
275286
276- entries := make([]maintenanceDirEntry, 0, limit)
287+ entries = make([]maintenanceDirEntry, 0, limit)
277288 buffer := make([]byte, 8192)
278289 for len(entries) < limit {
279290 count, err := syscall.ReadDirent(fd, buffer)
@@ -260,12 +260,23 @@ func (r *Repository) readMaintenanceDir(dir string, n int, purpose string) ([]ma
260 // readMaintenanceDirent uses the Linux getdents seek cookie from each record.260 // readMaintenanceDirent uses the Linux getdents seek cookie from each record.
261 // syscall.Dirent's Off field is the d_off member of linux_dirent64 and261 // syscall.Dirent's Off field is the d_off member of linux_dirent64 and
262 // syscall.Seek is lseek(2), as defined by Go's syscall Linux sources.262 // syscall.Seek is lseek(2), as defined by Go's syscall Linux sources.
263-func readMaintenanceDirent(dir string, limit int, cursor *maintenanceCursor) ([]maintenanceDirEntry, bool, error) {263+func readMaintenanceDirent(dir string, limit int, cursor *maintenanceCursor) (entries []maintenanceDirEntry, done bool, err error) {
264 file, err := os.Open(dir)264 file, err := os.Open(dir)
265 if err != nil {265 if err != nil {
266 return nil, false, err266 return nil, false, err
267 }267 }
268- defer file.Close()268+ defer func() {
269+ if closeErr := file.Close(); closeErr != nil {
270+ closeErr = fmt.Errorf("close maintenance directory %q: %w", dir, closeErr)
271+ if err != nil {
272+ err = errors.Join(err, closeErr)
273+ } else {
274+ entries = nil
275+ done = false
276+ err = closeErr
277+ }
278+ }
279+ }()
269 fd := int(file.Fd())280 fd := int(file.Fd())
270 if cursor.offset != 0 {281 if cursor.offset != 0 {
271 if _, err := syscall.Seek(fd, cursor.offset, io.SeekStart); err != nil {282 if _, err := syscall.Seek(fd, cursor.offset, io.SeekStart); err != nil {
@@ -273,7 +284,7 @@ func readMaintenanceDirent(dir string, limit int, cursor *maintenanceCursor) ([]
273 }284 }
274 }285 }
275 286
276- entries := make([]maintenanceDirEntry, 0, limit)287+ entries = make([]maintenanceDirEntry, 0, limit)
277 buffer := make([]byte, 8192)288 buffer := make([]byte, 8192)
278 for len(entries) < limit {289 for len(entries) < limit {
279 count, err := syscall.ReadDirent(fd, buffer)290 count, err := syscall.ReadDirent(fd, buffer)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close