-
Notifications
You must be signed in to change notification settings - Fork 52
fix: lazyBoundedReadCloser correctly close/seek #431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Benchmark Test ResultsBenchmark results from the latest changes vs base branch |
Signed-off-by: Keith Zantow <[email protected]>
7493937 to
0b61951
Compare
| if err != nil && errors.Is(err, io.EOF) { | ||
| // we've reached the end of the file, force a release of the file descriptor. If the file has already been | ||
| // closed, ignore the error. | ||
| if closeErr := d.file.Close(); !errors.Is(closeErr, os.ErrClosed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review note for self: d.Close() and d.file.Close() are different. d.Close() does not hoist from d.file.Close(). It's using: https://github.com/anchore/stereoscope/blob/main/pkg/file/lazy_bounded_read_closer.go#L53-L66
d.Close() calls d.file.Close() while also resetting d.file and d.reader to nil
closeErr cannot be os.ErrClosed given this err type is already accounted for by d.Close() and ignored.
@kzantow what was the original issue where you saw this behave weird when it wasn't reset?
I just want to know how this manifested in the wild so I can understand the change before 🟢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is evident in syft main branch, JDK binary detection against an image. For example:
go run ./cmd/syft ibmjava:8-jre@sha256:3588cd1cc9b8646fe03b3b15210e69b1b520f1321f8518b69c0e7013d702fd23 | grep '^java '
... returns no results. The reason for this is the java binary is read multiple times, calling .Seek here but stereoscope maintains a reference to the closed file, preventing further .Seek calls from opening it again, whereas d.Close sets this to nil, resulting in the next d.openFile() call to actually open the file again for reading. I don't think the error returned from Close should be returned, because this only occurs when EOF is returned and if EOF is not returned to the original caller, there are cases it may not proceed or retry and get in an infinite loop, etc..
Tests are passing in syft because they are using a directory source, not an image source. 😢
With this change, you should see:
go run ./cmd/syft ibmjava:8-jre@sha256:3588cd1cc9b8646fe03b3b15210e69b1b520f1321f8518b69c0e7013d702fd23 | grep '^java '
...
java 1.8.0-_2025_04_14_02_37-b00 binary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome - was able to reproduce on my end with the new change. TY for the walk through.
I don't think the error returned from Close should be returned, because this only occurs when EOF is returned and if EOF is not returned to the original caller, there are cases it may not proceed or retry and get in an infinite loop, etc..
Hard agree on the above change is 🟢 for me.
| // we've reached the end of the file, release of the file descriptor. continue to return EOF | ||
| // IMPORTANT: call d.Close to reset internal state | ||
| if closeErr := d.Close(); closeErr != nil { | ||
| log.Tracef("unable to close: %v: %v", d.path, closeErr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure the best behavior here, can certainly be convinced to change this... but this is only hit if we received EOF during the Read call, which will be returned to the caller and we're managing open file handles behind the scenes, so while an error closing here can indicate a problem, there's not much that a caller can do about this if it fails and they are already receiving an EOF, so should stop further reading anyway unless they Seek, at which time we need to be able to reopen the file and a serious problem like the file being removed will cause an error then, more appropriately, etc.
This PR fixes an issue where reading a lazyBoundedReadCloser is unable to seek due to being closed but not reset properly. In other words:
.Seek(0, io.SeekStart)attempts to calld.openFile(), but since there is a non-nil reference to a reader, it does not reopen the file but fails to seek and read from it afterwards instead of actually reopening it for reading.