Skip to content
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

stargzify: Pushing blobs fail with DIGEST_INVALID occasionally because of race #24

Closed
ktock opened this issue Oct 13, 2019 · 1 comment

Comments

@ktock
Copy link
Contributor

ktock commented Oct 13, 2019

Sometimes, stargzifying an image fails with DIGEST_INVALID.

$ ./stargzify -upgrade 127.0.0.1:5000/ubuntu:18.04
2019/10/13 14:37:24 No matching credentials were found, falling back on anonymous
2019/10/13 14:37:49 DIGEST_INVALID: provided digest did not match uploaded content; map[Digest:sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 Reason:map[]]

Especially when Writeing to the digester is slow, we can see this failure more frequently.

func (d *digester) Write(b []byte) (int, error) {
	time.Sleep(time.Duration(1) * time.Millisecond)
	n, err := d.h.Write(b)
	d.n += int64(n)
	return n, err
}

There is a race condition on the digester

The method layer.Compressed() calculates the digest(with Sum() method) immediately after the PipeWriter is unblocked.

func (l *layer) Compressed() (io.ReadCloser, error) {
	pr, pw := io.Pipe()

	// Convert input blob to stargz while computing diffid, digest, and size.
	go func() {
		w := stargz.NewWriter(pw)
		if err := w.AppendTar(l.rc); err != nil {
...
		if err := w.Close(); err != nil {
...
		l.digest = &v1.Hash{
			Algorithm: "sha256",
			Hex:       hex.EncodeToString(l.d.h.Sum(nil)),
		}
...
	}()

	return ioutil.NopCloser(io.TeeReader(pr, l.d)), nil

But even if the PipeWriter is unblocked, it isn't guaranteed that the TeeReader completes to write the data to the digester.

Accroding to the implementation of the TeeReader:

func (t *teeReader) Read(p []byte) (n int, err error) {
	n, err = t.r.Read(p)

        // ***** Region A *****

	if n > 0 {
		if n, err := t.w.Write(p[:n]); err != nil {
			return n, err
		}
	}
	return
}

Here, this TeeReader has the PipeReader as a reader and the digester as a writer.
In the Region A of the above, the write half(PipeWriter) is unblocked because reading from this PipeReader completes.
So the goroutine in the layer.Compressed() can immediately calculate the digest during the region A(before following t.w.Write() completes).
This race results in calculating an invalid(uncompleted) degest.

@ktock
Copy link
Contributor Author

ktock commented Oct 15, 2019

Fixed in #25. Thank you very much for merging.

@ktock ktock closed this as completed Oct 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant