Skip to content

Commit 8f1afaa

Browse files
[fix] Improve upload retry resilience (#292)
1 parent ee8fc4e commit 8f1afaa

17 files changed

Lines changed: 674 additions & 109 deletions

src/ByteSync.Client/Services/Communications/Transfers/Strategies/AzureBlobStorageUploadStrategy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public async Task<UploadFileResponse> UploadAsync(FileUploaderSlice slice, FileS
2121
try
2222
{
2323
var options = new BlobClientOptions();
24-
options.Retry.NetworkTimeout = TimeSpan.FromMinutes(1);
24+
options.Retry.NetworkTimeout = TimeSpan.FromMinutes(10);
2525

2626
slice.MemoryStream.Position = 0;
2727
var blob = new BlobClient(new Uri(storageLocation.Url), options);

src/ByteSync.Client/Services/Communications/Transfers/Strategies/CloudflareR2UploadStrategy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public async Task<UploadFileResponse> UploadAsync(FileUploaderSlice slice, FileS
2929
slice.MemoryStream.Position = 0;
3030

3131
using var httpClient = _httpClientFactory.CreateClient();
32-
httpClient.Timeout = TimeSpan.FromMinutes(1);
32+
httpClient.Timeout = Timeout.InfiniteTimeSpan;
3333
httpClient.DefaultRequestHeaders.ExpectContinue = false;
3434

3535
// Build ReadOnlyMemory without copying when possible; fallback to ToArray otherwise

src/ByteSync.Client/Services/Communications/Transfers/Strategies/UploadFailureClassifier.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System;
2+
using System.IO;
3+
using System.Net.Http;
4+
using System.Net.Sockets;
25
using System.Threading;
36
using ByteSync.Common.Business.Communications.Transfers;
47

@@ -12,12 +15,44 @@ public static UploadFileResponse Classify(Exception exception, CancellationToken
1215
{
1316
return UploadFileResponse.ClientCancellation(exception);
1417
}
15-
18+
1619
if (exception is OperationCanceledException)
1720
{
1821
return UploadFileResponse.ClientTimeout(exception);
1922
}
2023

24+
if (IsClientNetworkError(exception))
25+
{
26+
return UploadFileResponse.ClientNetworkError(exception);
27+
}
28+
2129
return UploadFileResponse.Failure(500, exception);
2230
}
31+
32+
private static bool IsClientNetworkError(Exception exception)
33+
{
34+
if (exception is not HttpRequestException and not IOException and not SocketException)
35+
{
36+
return false;
37+
}
38+
39+
var current = exception;
40+
while (current != null)
41+
{
42+
if (current is SocketException socketException)
43+
{
44+
return socketException.SocketErrorCode is SocketError.ConnectionReset
45+
or SocketError.ConnectionAborted
46+
or SocketError.TimedOut
47+
or SocketError.NetworkDown
48+
or SocketError.NetworkUnreachable
49+
or SocketError.HostDown
50+
or SocketError.HostUnreachable;
51+
}
52+
53+
current = current.InnerException;
54+
}
55+
56+
return false;
57+
}
2358
}

src/ByteSync.Client/Services/Communications/Transfers/Uploading/AdaptiveUploadController.cs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class AdaptiveUploadController : IAdaptiveUploadController
1414
private const int MAX_CHUNK_SIZE_BYTES = 16 * 1024 * 1024; // 16 MB
1515
private const int MIN_PARALLELISM = 2;
1616
private const int MAX_PARALLELISM = 4;
17-
private const int CLIENT_TIMEOUTS_BEFORE_DOWNSCALE = 2;
17+
private const int CLIENT_NETWORK_ISSUES_BEFORE_DOWNSCALE = 2;
1818

1919
private const double MULTIPLIER_2_X = 2.0;
2020
private const double MULTIPLIER_1_75_X = 1.75;
@@ -37,7 +37,7 @@ public class AdaptiveUploadController : IAdaptiveUploadController
3737
private readonly Queue<long> _recentBytes;
3838
private int _successesInWindow;
3939
private int _windowSize;
40-
private int _consecutiveClientTimeouts;
40+
private int _consecutiveClientNetworkIssues;
4141
private readonly ILogger<AdaptiveUploadController> _logger;
4242
private readonly object _syncRoot = new();
4343

@@ -90,17 +90,17 @@ public void RecordUploadResult(UploadResult uploadResult)
9090
{
9191
if (uploadResult.FailureKind == UploadFailureKind.ClientCancellation)
9292
{
93-
_consecutiveClientTimeouts = 0;
93+
_consecutiveClientNetworkIssues = 0;
9494
return;
9595
}
9696

97-
if (uploadResult.FailureKind == UploadFailureKind.ClientTimeout)
97+
if (uploadResult.FailureKind is UploadFailureKind.ClientTimeout or UploadFailureKind.ClientNetworkError)
9898
{
99-
HandleClientTimeout(uploadResult.FileId);
99+
HandleClientNetworkIssue(uploadResult.FileId, uploadResult.FailureKind);
100100
return;
101101
}
102102

103-
_consecutiveClientTimeouts = 0;
103+
_consecutiveClientNetworkIssues = 0;
104104

105105
EnqueueSample(uploadResult.Elapsed, uploadResult.IsSuccess, uploadResult.ActualBytes);
106106

@@ -162,26 +162,27 @@ private void EnqueueSample(TimeSpan elapsed, bool isSuccess, long actualBytes)
162162
}
163163
}
164164

165-
private void HandleClientTimeout(string? fileId)
165+
private void HandleClientNetworkIssue(string? fileId, UploadFailureKind failureKind)
166166
{
167-
_consecutiveClientTimeouts += 1;
168-
if (_consecutiveClientTimeouts < CLIENT_TIMEOUTS_BEFORE_DOWNSCALE)
167+
_consecutiveClientNetworkIssues += 1;
168+
if (_consecutiveClientNetworkIssues < CLIENT_NETWORK_ISSUES_BEFORE_DOWNSCALE)
169169
{
170170
_logger.LogDebug(
171-
"Adaptive: file {FileId} client timeout {TimeoutCount}/{Threshold}. Waiting before downscale",
171+
"Adaptive: file {FileId} client network issue {FailureKind} {IssueCount}/{Threshold}. Waiting before downscale",
172172
fileId ?? "-",
173-
_consecutiveClientTimeouts,
174-
CLIENT_TIMEOUTS_BEFORE_DOWNSCALE);
173+
failureKind,
174+
_consecutiveClientNetworkIssues,
175+
CLIENT_NETWORK_ISSUES_BEFORE_DOWNSCALE);
175176

176177
return;
177178
}
178179

179180
_logger.LogInformation(
180-
"Adaptive: file {FileId} client timeout threshold reached ({TimeoutCount}). Downscaling upload settings",
181+
"Adaptive: file {FileId} client network issue threshold reached ({IssueCount}). Downscaling upload settings",
181182
fileId ?? "-",
182-
_consecutiveClientTimeouts);
183-
_consecutiveClientTimeouts = 0;
184-
Downscale(fileId, "client timeouts");
183+
_consecutiveClientNetworkIssues);
184+
_consecutiveClientNetworkIssues = 0;
185+
Downscale(fileId, "client network issues");
185186
}
186187

187188
private bool HandleBandwidthReset(bool isSuccess, int? statusCode)
@@ -395,7 +396,7 @@ private void ResetState()
395396
_currentChunkSizeBytes = Math.Clamp(INITIAL_CHUNK_SIZE_BYTES, MIN_CHUNK_SIZE_BYTES, MAX_CHUNK_SIZE_BYTES);
396397
_currentParallelism = MIN_PARALLELISM;
397398
_windowSize = _currentParallelism;
398-
_consecutiveClientTimeouts = 0;
399+
_consecutiveClientNetworkIssues = 0;
399400
}
400401

401402
ResetWindow();

src/ByteSync.Client/Services/Communications/Transfers/Uploading/FileSlicer.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ public async Task SliceAndEncryptAsync(SharedFileDefinition sharedFileDefinition
6363
_semaphoreSlim.Release();
6464
}
6565

66-
await _availableSlices.Writer.WriteAsync(fileUploaderSlice);
66+
if (!await TryWriteSliceAsync(fileUploaderSlice))
67+
{
68+
return;
69+
}
6770
}
6871
else
6972
{
@@ -126,7 +129,10 @@ public async Task SliceAndEncryptAdaptiveAsync(SharedFileDefinition sharedFileDe
126129
_semaphoreSlim.Release();
127130
}
128131

129-
await _availableSlices.Writer.WriteAsync(fileUploaderSlice);
132+
if (!await TryWriteSliceAsync(fileUploaderSlice))
133+
{
134+
return;
135+
}
130136
}
131137
else
132138
{
@@ -152,4 +158,20 @@ public async Task SliceAndEncryptAdaptiveAsync(SharedFileDefinition sharedFileDe
152158
_availableSlices.Writer.TryComplete(ex);
153159
}
154160
}
161+
162+
private async Task<bool> TryWriteSliceAsync(FileUploaderSlice fileUploaderSlice)
163+
{
164+
try
165+
{
166+
await _availableSlices.Writer.WriteAsync(fileUploaderSlice);
167+
168+
return true;
169+
}
170+
catch (ChannelClosedException) when (_exceptionOccurred.WaitOne(0))
171+
{
172+
await fileUploaderSlice.MemoryStream.DisposeAsync();
173+
174+
return false;
175+
}
176+
}
155177
}

src/ByteSync.Client/Services/Communications/Transfers/Uploading/FileUploadCoordinator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class FileUploadCoordinator : IFileUploadCoordinator
1212
private readonly ManualResetEvent _uploadingIsFinished;
1313
private readonly ManualResetEvent _exceptionOccurred;
1414
private readonly ILogger<FileUploadCoordinator> _logger;
15-
private const int CHANNEL_CAPACITY = 8;
15+
private const int CHANNEL_CAPACITY = 2;
1616

1717
public FileUploadCoordinator(ILogger<FileUploadCoordinator> logger)
1818
{

0 commit comments

Comments
 (0)