Skip to content

Commit e3f6c7d

Browse files
authored
Support parallel upload of blobs (#210)
* parallel upload * use concurrentdictionary instead
1 parent 7116882 commit e3f6c7d

File tree

2 files changed

+14
-14
lines changed

2 files changed

+14
-14
lines changed

Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Diagnostics.CodeAnalysis;
1+
using System.Collections.Concurrent;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Net;
34
using System.Net.Http.Headers;
45
using System.Text;
@@ -19,7 +20,7 @@ private record AuthInfo(Uri Realm, string Service, string Scope);
1920
/// <summary>
2021
/// Cache of most-recently-recieved token for each server.
2122
/// </summary>
22-
private static Dictionary<string, string> TokenCache = new();
23+
private static ConcurrentDictionary<string, string> TokenCache = new();
2324

2425
/// <summary>
2526
/// the www-authenticate header must have realm, service, and scope information, so this method parses it into that shape if present
@@ -112,8 +113,10 @@ private async Task<string> GetTokenAsync(Uri realm, string service, string scope
112113
throw new ArgumentException("Could not deserialize token from JSON");
113114
}
114115

115-
// save the retrieved token in the cache
116-
TokenCache[realm.Host] = token.ResolvedToken;
116+
// save the retrieved token in the cache.
117+
// if we encounter a previous token (very possible due to concurrent upload)
118+
// use the more recent token.
119+
TokenCache.AddOrUpdate(realm.Host, token.ResolvedToken, (previous, current) => current);
117120
return token.ResolvedToken;
118121
}
119122

Microsoft.NET.Build.Containers/Registry.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,18 @@ public async Task Push(Image x, string name, string? tag, string baseName, Actio
176176
tag ??= "latest";
177177

178178
using HttpClient client = GetClient();
179-
180-
foreach (var descriptor in x.LayerDescriptors)
181-
{
179+
var reg = this;
180+
await Task.WhenAll(x.LayerDescriptors.Select(async descriptor => {
182181
string digest = descriptor.Digest;
183182
logProgressMessage($"Uploading layer {digest} to registry");
184-
if (await BlobAlreadyUploaded(name, digest, client))
183+
if (await reg.BlobAlreadyUploaded(name, digest, client))
185184
{
186185
logProgressMessage($"Layer {digest} already existed");
187-
continue;
186+
return;
188187
}
189188

190189
// Blob wasn't there; can we tell the server to get it from the base image?
191-
HttpResponseMessage pushResponse = await client.PostAsync(new Uri(BaseUri, $"/v2/{name}/blobs/uploads/?mount={digest}&from={baseName}"), content: null);
190+
HttpResponseMessage pushResponse = await client.PostAsync(new Uri(reg.BaseUri, $"/v2/{name}/blobs/uploads/?mount={digest}&from={baseName}"), content: null);
192191

193192
if (pushResponse.StatusCode != HttpStatusCode.Created)
194193
{
@@ -202,10 +201,10 @@ public async Task Push(Image x, string name, string? tag, string baseName, Actio
202201
// Ensure the blob is available locally
203202
await x.originatingRegistry.Value.DownloadBlob(x.OriginatingName, descriptor);
204203
// Then push it to the destination registry
205-
await Push(Layer.FromDescriptor(descriptor), name, logProgressMessage);
204+
await reg.Push(Layer.FromDescriptor(descriptor), name, logProgressMessage);
206205
logProgressMessage($"Finished uploading layer {digest} to registry");
207206
}
208-
}
207+
}));
209208

210209
using (MemoryStream stringStream = new MemoryStream(Encoding.UTF8.GetBytes(x.config.ToJsonString())))
211210
{
@@ -220,14 +219,12 @@ public async Task Push(Image x, string name, string? tag, string baseName, Actio
220219
HttpContent manifestUploadContent = new StringContent(x.manifest.ToJsonString());
221220
manifestUploadContent.Headers.ContentType = new MediaTypeHeaderValue(DockerManifestV2);
222221
var putResponse = await client.PutAsync(new Uri(BaseUri, $"/v2/{name}/manifests/{manifestDigest}"), manifestUploadContent);
223-
string putresponsestr = await putResponse.Content.ReadAsStringAsync();
224222

225223
if (!putResponse.IsSuccessStatusCode)
226224
{
227225
string jsonResponse = await putResponse.Content.ReadAsStringAsync();
228226
throw new ContainerHttpException("Registry push failed.", putResponse.RequestMessage?.RequestUri?.ToString(), jsonResponse);
229227
}
230-
231228
logProgressMessage($"Uploaded manifest to registry");
232229

233230
logProgressMessage($"Uploading tag {tag} to registry");

0 commit comments

Comments
 (0)