feat(sync): fix review comments + extras

Signed-off-by: Vishwas Rajashekar <dev@vrajashkr.com>
This commit is contained in:
Vishwas Rajashekar
2026-05-17 17:01:43 +05:30
parent 3adf36a6c7
commit fa39761700
10 changed files with 84 additions and 54 deletions
+14 -4
View File
@@ -75,11 +75,16 @@ func (cbr *ChunkedBlobReader) Read(buff []byte) (int, error) {
numBytesRead, err := io.CopyN(multiWriter, cbr.InFlightReader, cbr.chunkSizeBytes)
if err != nil {
if !errors.Is(err, io.EOF) {
// upstream download error
cbr.logger.Error().Err(err).Msg("failed to copy from in flight reader")
copy(buff, internalBuff.Bytes())
cbr.chunksMu.Unlock()
return int(numBytesRead), err
// drain all clients and close their channels
for clientId := range cbr.clients {
cbr.Unsubscribe(clientId)
}
return -1, err
}
}
@@ -141,8 +146,13 @@ func (cbr *ChunkedBlobReader) Unsubscribe(clientId int) {
cbr.clientMu.Unlock()
}()
delete(cbr.clients, clientId)
cbr.numClientsTotal--
channel, ok := cbr.clients[clientId]
if ok {
close(channel)
cbr.numClientsTotal--
delete(cbr.clients, clientId)
}
}
func (cbr *ChunkedBlobReader) ToBReader() *blob.BReader {
+8 -4
View File
@@ -6,6 +6,7 @@ import (
"os"
"sync"
zerr "zotregistry.dev/zot/v2/errors"
"zotregistry.dev/zot/v2/pkg/log"
)
@@ -37,7 +38,7 @@ func NewInFlightBlobCopier(
}
func (ifbc *InFlightBlobCopier) Copy() error {
ifbc.log.Info().Msg("starting inflight copy")
ifbc.log.Debug().Str("onDiskPath", ifbc.onDiskPath).Msg("starting inflight copy")
onDiskFile, err := os.Open(ifbc.onDiskPath)
if err != nil {
@@ -51,12 +52,15 @@ func (ifbc *InFlightBlobCopier) Copy() error {
chunkChan := make(chan int64, 1)
id := ifbc.Source.Subscribe(chunkChan)
defer ifbc.Source.Unsubscribe(id)
defer close(chunkChan)
for {
latestChunkNum := <-chunkChan
latestChunkNum, ok := <-chunkChan
if !ok {
ifbc.log.Error().Str("onDiskPath", ifbc.onDiskPath).Msg("failed to download from upstream, aborting inflight copy")
return zerr.ErrSyncUpstreamDownloadFailed
}
ifbc.Lock()
if latestChunkNum <= ifbc.numChunksCopied {
+30 -27
View File
@@ -53,22 +53,26 @@ func (onDemand *BaseOnDemand) StreamManager() StreamManager {
return onDemand.streamManager
}
func (onDemand *BaseOnDemand) FetchManifest(ctx context.Context, repo, reference string) (manifest.Manifest, error) {
// An image might already be streaming in which case, just return the one in cache.
if onDemand.streamManager != nil {
manifest, ok := onDemand.streamManager.StreamingImageManifest(repo, reference)
if ok {
onDemand.log.Debug().Str("repo", repo).Str("reference", reference).
Msg("streaming manifest already present in cache.")
// FetchManifestForStream directly fetches the manifest from the upstream services and prepares the image
// for streaming.
// This is only intended for use with streaming sync.
func (onDemand *BaseOnDemand) FetchManifestForStream(
ctx context.Context, repo, reference string,
) (manifest.Manifest, error) {
// If an image is already streaming, return the one in cache.
// There is no need to start a new background sync if the manifest is already cached.
cachedManifest, ok := onDemand.streamManager.StreamingImageManifest(repo, reference)
if ok {
onDemand.log.Debug().Str("repo", repo).Str("reference", reference).
Msg("streaming manifest already present in cache.")
return manifest, nil
}
return cachedManifest, nil
}
var manifest manifest.Manifest
for _, service := range onDemand.services {
onDemand.log.Info().Str("repo", repo).Str("ref", reference).Msg("attempting to fetch manifest")
onDemand.log.Debug().Str("repo", repo).Str("ref", reference).Msg("attempting to fetch manifest")
fetchedManifest, err := service.FetchManifest(ctx, repo, reference)
if err != nil {
onDemand.log.Error().Err(err).Msg("failed to fetch manifest from service")
@@ -84,27 +88,26 @@ func (onDemand *BaseOnDemand) FetchManifest(ctx context.Context, repo, reference
return nil, zerr.ErrBlobNotFound
}
if onDemand.streamManager != nil {
onDemand.log.Debug().Str("repo", repo).Str("reference", reference).
Msg("storing image for streaming.")
onDemand.log.Debug().Str("repo", repo).Str("reference", reference).
Msg("storing image for streaming")
err := onDemand.streamManager.StoreImageForStreaming(repo, reference, manifest)
if err != nil {
onDemand.log.Error().Err(err).Str("repo", repo).Str("reference", reference).
Msg("failed to store manifest for streaming")
err := onDemand.streamManager.StoreImageForStreaming(repo, reference, manifest)
if err != nil {
onDemand.log.Error().Err(err).Str("repo", repo).Str("reference", reference).
Msg("failed to store manifest for streaming")
return nil, err
}
// sync the image in the background
go func() {
if errSync := onDemand.SyncImage(ctx, repo, reference); errSync != nil {
onDemand.log.Err(errSync).Str("repository", repo).Str("reference", reference).
Msg("failed to sync image")
}
}()
return nil, err
}
// sync the image in the background
go func() {
syncCtx := context.WithoutCancel(ctx)
if errSync := onDemand.SyncImage(syncCtx, repo, reference); errSync != nil {
onDemand.log.Err(errSync).Str("repository", repo).Str("reference", reference).
Msg("failed to sync image")
}
}()
return manifest, nil
}
+3 -1
View File
@@ -20,7 +20,9 @@ func (onDemand *BaseOnDemand) SyncReferrers(ctx context.Context, repo string,
return nil
}
func (onDemand *BaseOnDemand) FetchManifest(ctx context.Context, repo, reference string) (manifest.Manifest, error) {
func (onDemand *BaseOnDemand) FetchManifestForStream(
ctx context.Context, repo, reference string,
) (manifest.Manifest, error) {
return manifest.New()
}
+1 -1
View File
@@ -521,7 +521,7 @@ func (service *BaseService) syncRef(ctx context.Context, localRepo string, remot
copyOpts := []regclient.ImageOpts{}
if service.streamManager != nil {
service.log.Info().Str("repo", localRepo).Str("reference", remoteImageRef.Tag).
service.log.Debug().Str("repo", localRepo).Str("reference", remoteImageRef.Tag).
Msg("streaming is enabled. Enabling reader hook")
copyOpts = append(copyOpts, regclient.ImageWithBlobReaderHook(service.streamManager.StreamingBlobReader))
}
+1 -1
View File
@@ -41,7 +41,7 @@ type ChunkingStreamManager struct {
}
func NewChunkingStreamManager(config *config.Config, logger log.Logger) *ChunkingStreamManager {
store := NewLocalTempStore(path.Join(config.Storage.RootDirectory, "stream"))
store := NewLocalTempStore(path.Join(config.Storage.RootDirectory, "_stream"), logger)
return &ChunkingStreamManager{
tempStore: store,
+12 -5
View File
@@ -2,11 +2,12 @@ package sync
import (
"errors"
"fmt"
"os"
"path"
godigest "github.com/opencontainers/go-digest"
"zotregistry.dev/zot/v2/pkg/log"
)
type StreamTempStore interface {
@@ -15,23 +16,27 @@ type StreamTempStore interface {
type LocalTempStore struct {
rootPath string
logger log.Logger
}
func NewLocalTempStore(rootDir string) *LocalTempStore {
func NewLocalTempStore(rootDir string, logger log.Logger) *LocalTempStore {
_, err := os.Stat(rootDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
err := os.MkdirAll(rootDir, 0o755)
if err != nil {
fmt.Println("failed to create root dir " + err.Error())
// If the root directory cannot be created, log a fatal error and exit.
logger.Fatal().Str("rootDir", rootDir).Err(err).Msg("failed to create root dir for stream temp store")
}
} else {
fmt.Println("failed to stat root dir " + err.Error())
// If there is an error other than "not exists", log a fatal error and exit.
logger.Fatal().Str("rootDir", rootDir).Err(err).Msg("failed to stat root dir for stream temp store")
}
}
return &LocalTempStore{
rootPath: rootDir,
logger: logger,
}
}
@@ -41,7 +46,9 @@ func (lts *LocalTempStore) BlobPath(digest godigest.Digest) string {
if err != nil && errors.Is(err, os.ErrNotExist) {
err := os.MkdirAll(parentDir, 0o755)
if err != nil {
fmt.Println("failed to create directory " + err.Error())
// It is safe to not use fatal here as the stream will hit an error while
// trying to use the blob path, and the error will be handled there.
lts.logger.Error().Str("parentDir", parentDir).Err(err).Msg("failed to create directory")
}
}