diff --git a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladClient.cs b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladClient.cs index bed9460..d88f5f9 100644 --- a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladClient.cs +++ b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladClient.cs @@ -98,14 +98,39 @@ public async Task> GetAllFoldersAsync(string token, Cancel [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct) { const int pageSize = 1000; + const int maxAttempts = 5; var offset = 0; var filterSuffix = archivedOnly ? "&filter=archived=true" : ""; while (true) { - using var req = Build(HttpMethod.Get, $"{path}?limit={pageSize}&offset={offset}{filterSuffix}", token); - using var res = await _http.SendAsync(req, ct); - res.EnsureSuccessStatusCode(); - var page = await res.Content.ReadFromJsonAsync>(Json, ct); + MsListResponse? page = null; + Exception? lastErr = null; + for (var attempt = 1; attempt <= maxAttempts; attempt++) + { + try + { + using var req = Build(HttpMethod.Get, $"{path}?limit={pageSize}&offset={offset}{filterSuffix}", token); + using var res = await _http.SendAsync(req, ct); + res.EnsureSuccessStatusCode(); + page = await res.Content.ReadFromJsonAsync>(Json, ct); + lastErr = null; + break; + } + catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or IOException) + { + lastErr = ex; + if (attempt == maxAttempts) break; + // Exponential-ish backoff: 2s, 4s, 8s, 16s. + await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)), ct); + } + } + if (lastErr is not null) + { + // Re-throw after retries so the caller sees a real failure instead of silent halt. + throw new InvalidOperationException( + $"MoySklad paging failed at {path} offset={offset} after {maxAttempts} attempts: {lastErr.Message}", + lastErr); + } if (page is null || page.Rows.Count == 0) yield break; foreach (var row in page.Rows) yield return row; if (page.Rows.Count < pageSize) yield break; diff --git a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs index 89ec7bb..0f9b8ed 100644 --- a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs +++ b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs @@ -273,8 +273,9 @@ await foreach (var p in _client.StreamProductsAsync(token, ct)) created++; } - // Flush periodically to keep change tracker light. - if ((created + updated) % 500 == 0) await _db.SaveChangesAsync(ct); + // Flush чаще (каждые 100) чтобы при сетевом обрыве на следующей странице + // мы сохранили как можно больше и смогли безопасно продолжить с overwrite. + if ((created + updated) % 100 == 0) await _db.SaveChangesAsync(ct); } catch (Exception ex) {