@@ -151,12 +151,9 @@ type S3DownloadAPI interface {
151151 DownloadObject (ctx context.Context , params * transfermanager.DownloadObjectInput , optFns ... func (* transfermanager.Options )) (* transfermanager.DownloadObjectOutput , error )
152152}
153153
154- // S3HeadAPI reads an object's metadata without reading the object itself,
155- // including the checksum S3 stores for it. The real *s3.Client satisfies this
156- // implicitly.
157- //
158- // The stored checksum is only returned when the request sets ChecksumMode to
159- // ChecksumModeEnabled.
154+ // S3HeadAPI reads an object's metadata, including the checksum S3 stores for
155+ // it, which is returned only when the request sets ChecksumMode to
156+ // ChecksumModeEnabled. The real *s3.Client satisfies this implicitly.
160157type S3HeadAPI interface {
161158 HeadObject (ctx context.Context , params * s3.HeadObjectInput , optFns ... func (* s3.Options )) (* s3.HeadObjectOutput , error )
162159}
@@ -400,9 +397,8 @@ func formatLambdaLastModified(lastModified string) (time.Time, error) {
400397 return time .Parse (layout , lastModified )
401398}
402399
403- // decodeBase64Sha256 converts a Base64-encoded SHA256 digest into the hex form
404- // Kosli fingerprints use. AWS reports stored digests in Base64: Lambda's
405- // CodeSha256 and an S3 object's checksum both arrive this way.
400+ // decodeBase64Sha256 converts a Base64 SHA256 digest, the form AWS reports
401+ // stored digests in, into the hex form Kosli fingerprints use.
406402func decodeBase64Sha256 (fingerprint string ) (string , error ) {
407403 sha256base64 , err := base64 .StdEncoding .DecodeString (fingerprint )
408404 if err != nil {
@@ -479,8 +475,7 @@ func getS3DataFromClient(client S3API, bucket string, includePaths, includeRegex
479475}
480476
481477// getS3DataWithSource lists and filters the bucket, then fingerprints what is
482- // left with digests from source. Everything but the digest source is shared, so
483- // the sources cannot disagree on which objects a snapshot covers.
478+ // left with digests from source.
484479func getS3DataWithSource (client S3API , source s3DigestSource , bucket string , includePaths , includeRegex , excludePaths , excludeRegex []string , limits DownloadLimits , logger * logger.Logger ) ([]* S3Data , error ) {
485480 s3Data := []* S3Data {}
486481
@@ -545,12 +540,10 @@ type DownloadLimits struct {
545540// Lambda's default /tmp, and part buffers near 320 MiB of memory.
546541var DefaultDownloadLimits = DownloadLimits {Concurrency : 8 , BytesInFlight : 512 << 20 }
547542
548- // DefaultMetadataConcurrency is how many checksum reads run at once when a
549- // bucket is fingerprinted from S3 metadata. A HeadObject holds no part buffers
550- // and no temp disk, so the download default says nothing about it. At a 30 ms
551- // round trip, 32 in flight is around a thousand objects a second: a million in
552- // under twenty minutes, and still well under the 5,500 reads a second S3
553- // supports per prefix. The adaptive retryer absorbs throttling beyond that.
543+ // DefaultMetadataConcurrency is how many checksum reads run at once when
544+ // fingerprinting from S3 metadata. A HeadObject holds no part buffers or temp
545+ // disk, so it is sized against S3's request rate instead: 32 at a 30 ms round
546+ // trip is about 1,000 reads a second, well under the 5,500 S3 allows per prefix.
554547const DefaultMetadataConcurrency = 32
555548
556549// listMatchingS3Objects lists the bucket, dropping folder markers and keys the
@@ -602,18 +595,15 @@ func listMatchingS3Objects(client S3ListAPI, bucket string, includePaths []strin
602595 return objects , nil
603596}
604597
605- // s3DigestSource is where the fingerprint pipeline gets each object's content
606- // sha256 once the tree is known. Content mode downloads the object into the
607- // pipeline's temp dir and hashes it; a source that reads S3's stored checksum
608- // never touches the disk. Everything else -- key rule, .kosli_ignore, the tree
609- // walk -- is shared, so the two sources cannot fingerprint the same bucket
610- // differently.
598+ // s3DigestSource supplies each object's content sha256 to the fingerprint
599+ // pipeline. It is the only part that varies by source, so two sources cannot
600+ // fingerprint the same bucket differently.
611601type s3DigestSource struct {
612- // sha256 returns the hex digest of one object's content. tempDir is scratch
613- // space the pipeline owns and removes when it is done .
602+ // sha256 returns the hex digest of an object's content. tempDir is scratch
603+ // space the pipeline removes afterwards .
614604 sha256 func (ctx context.Context , tempDir string , object s3Object ) (string , error )
615- // usesDisk reports whether an object's listed size occupies temp disk while
616- // sha256 runs, and so counts against DownloadLimits.BytesInFlight.
605+ // usesDisk means an object's listed size occupies temp disk while sha256
606+ // runs, so it counts against DownloadLimits.BytesInFlight.
617607 usesDisk bool
618608}
619609
@@ -627,24 +617,19 @@ func downloadDigests(downloader S3DownloadAPI, bucket string, logger *logger.Log
627617 }
628618}
629619
630- // fingerprintS3Objects fingerprints the objects as the directory their keys
631- // describe, downloading each one to an anonymous temp file, hashing it and
632- // removing it. See fingerprintS3Tree for the pipeline.
620+ // fingerprintS3Objects fingerprints the objects with the download source.
633621func fingerprintS3Objects (downloader S3DownloadAPI , bucket string , objects []s3Object , limits DownloadLimits , logger * logger.Logger ) (string , string , error ) {
634622 return fingerprintS3Tree (downloader , downloadDigests (downloader , bucket , logger ), bucket , objects , limits , logger )
635623}
636624
637625// fingerprintS3Tree fingerprints the objects as the directory their keys
638- // describe, without ever using a key as a local file name. Each object's
639- // content sha256 comes from source; the fingerprint is then computed from the
640- // (key, sha256) pairs by digest.VirtualDirSha256, which reproduces what
641- // digest.DirSha256 gives the same tree on disk. A single object is
642- // fingerprinted as that file and named after it, as before.
626+ // describe, without using a key as a local file name: digest.VirtualDirSha256
627+ // combines the (key, sha256) pairs from source exactly as digest.DirSha256
628+ // would on disk. A single object is fingerprinted as that file and named
629+ // after it.
643630//
644- // A root .kosli_ignore is always downloaded first, whatever the source, because
645- // its rules decide which other objects take part; objects the rules exclude are
646- // not fetched at all. The remaining objects are fetched in parallel within
647- // limits, and the first failure cancels the rest.
631+ // The root .kosli_ignore is always downloaded, whatever the source, because its
632+ // rules decide which other objects are fetched at all.
648633func fingerprintS3Tree (downloader S3DownloadAPI , source s3DigestSource , bucket string , objects []s3Object , limits DownloadLimits , logger * logger.Logger ) (string , string , error ) {
649634 keys := make ([]string , len (objects ))
650635 for i , object := range objects {
@@ -675,8 +660,7 @@ func fingerprintS3Tree(downloader S3DownloadAPI, source s3DigestSource, bucket s
675660 // when the objects were laid out on disk.
676661 if file , ok := digest .SingleVirtualFile (files ); ok {
677662 fetch := source
678- // The root ignore file is always downloaded, so no source ever asks it
679- // for a checksum -- even alone, when its rules have nothing to decide.
663+ // The ignore file is downloaded even when alone, so no source needs its checksum.
680664 if file .Path == digest .IgnoreFileName {
681665 fetch = downloadDigests (downloader , bucket , logger )
682666 }
@@ -755,9 +739,8 @@ func ignoreRuleError(err error) error {
755739// source and writes each into files at the same index. A fixed worker pool
756740// bounds fetches and goroutines alike, a weighted semaphore bounds the listed
757741// bytes of sources that use the disk, and the first transport error cancels the
758- // context so nothing further starts. An unusableChecksumError is about one
759- // object rather than the connection, so those are collected and reported
760- // together once the rest have run.
742+ // context so nothing further starts. unusableChecksumErrors are collected
743+ // and reported together instead.
761744func fetchS3DigestsInParallel (source s3DigestSource , tempDir string , objects []s3Object , indexes []int ,
762745 files []digest.VirtualFile , limits DownloadLimits , logger * logger.Logger ) error {
763746 ctx , cancel := context .WithCancel (context .Background ())
@@ -786,7 +769,6 @@ func fetchS3DigestsInParallel(source s3DigestSource, tempDir string, objects []s
786769 for i := range work {
787770 object := objects [i ]
788771 // An object larger than the budget takes all of it and so runs alone.
789- // A source that never touches the disk owes the budget nothing.
790772 var weight int64
791773 if source .usesDisk {
792774 weight = max (min (object .size , limits .BytesInFlight ), 1 )
0 commit comments