fix: propagate migration body read errors to the database driver - #1417
Open
milanobrtlik wants to merge 1 commit into
Open
fix: propagate migration body read errors to the database driver#1417milanobrtlik wants to merge 1 commit into
milanobrtlik wants to merge 1 commit into
Conversation
Migration.Buffer closes bufferWriter in a deferred call so that readers of BufferedBody can never block (golang-migrate#1308). Because io.PipeWriter.Close closes with a nil error, the reader observes a clean io.EOF even when reading the migration body failed. The read error itself only reaches m.logErr in the goroutine started by readUp/readDown, and logErr is a no-op when no logger is configured. So on a failed body read runMigrations hands the driver a truncated (or empty) body, Run returns nil, and the migration is recorded as applied and clean: SetVersion(target, dirty=true) Run(BufferedBody) -> reads EOF, executes partial SQL, returns nil SetVersion(target, dirty=false) Close the pipe with the read error instead, so the driver fails, the error reaches the caller, and the version is correctly left dirty.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #1308.
Problem
Migration.bufferWriteris the write half of anio.PipeandMigration.BufferedBodyis the read half thatrunMigrationspasses todatabase.Driver.Run.#1308 correctly made
Buffer()always closebufferWritervia a deferred call, which fixed the reported case of a driver blocking forever onBufferedBody. However,io.PipeWriter.Close()closes with a nil error, so the reader observes a cleanio.EOF— including when reading the migration body actually failed.Both error returns in
Buffer()are affected:The error returned by
Buffer()does not compensate for this, becauseBuffer()runs in a goroutine started byreadUp/readDownwhose only handling ism.logErr(err)— andlogErris a no-op when no logger is configured, which is the default for library use:So when a source fails mid-read (S3, GCS, GitHub and other network-backed sources are the realistic trigger),
runMigrationsdoes:The result is that a partially-read migration is executed and then recorded as fully applied and clean. Nothing fails and nothing is logged, so the next
upcontinues from a schema that was never fully migrated. That is worse than the migration failing outright, where the dirty flag at least stops the next run.Fix
Close the pipe with the read error instead of a nil error, so the failure reaches the driver:
Runthen returns the error,runMigrationspropagates it to the caller, and the version row is correctly left dirty. The writer is still closed unconditionally, so the guarantee added in #1308 is preserved.This required changing the unexported field
bufferWriterfromio.WriteCloserto*io.PipeWriterto reachCloseWithError. The field is private and only referenced insidemigration.go;NewMigrationalready assigns it fromio.Pipe(). There is no change to the exported API.Tests
TestBufferPropagatesReadErrorcovers a body that fails before any data is read, after a partial read, and after the buffer is filled — the last one exercising theWriteToreturn site rather thanPeek. Each asserts that the error surfaces to a reader ofBufferedBody, which is what a database driver does with it.The test fails on
master(the read returns<nil>, i.e. a clean EOF with truncated content) and passes with this change. No Docker required.