Skip to content

Commit 48c5f1e

Browse files
committed
add failed sync dump which will export the commits, the entity which the update failed on and the error message
1 parent f63ca54 commit 48c5f1e

File tree

3 files changed

+51
-15
lines changed

3 files changed

+51
-15
lines changed

src/SIL.Harmony/CrdtConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ private void JsonTypeModifier(JsonTypeInfo typeInfo)
7272

7373
public bool RemoteResourcesEnabled { get; private set; }
7474
public string LocalResourceCachePath { get; set; } = Path.GetFullPath("./localResourceCache");
75+
public string FailedSyncOutputPath { get; set; } = Path.GetFullPath("./failedSyncs");
76+
7577
public void AddRemoteResourceEntity(string? cachePath = null)
7678
{
7779
RemoteResourcesEnabled = true;

src/SIL.Harmony/CrdtKernel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public static IServiceCollection AddCrdtData<TContext>(this IServiceCollection s
2626
provider.GetRequiredService<CrdtRepository>(),
2727
provider.GetRequiredService<JsonSerializerOptions>(),
2828
provider.GetRequiredService<IHybridDateTimeProvider>(),
29-
provider.GetRequiredService<IOptions<CrdtConfig>>()
29+
provider.GetRequiredService<IOptions<CrdtConfig>>(),
30+
provider.GetRequiredService<ILogger<DataModel>>()
3031
));
3132
//must use factory method because ResourceService constructor is internal
3233
services.AddScoped<ResourceService>(provider => new ResourceService(

src/SIL.Harmony/DataModel.cs

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Text.Json;
22
using SIL.Harmony.Core;
33
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.Extensions.Logging;
45
using Microsoft.Extensions.Options;
56
using SIL.Harmony.Changes;
67
using SIL.Harmony.Db;
@@ -22,17 +23,20 @@ public class DataModel : ISyncable, IAsyncDisposable
2223
private readonly JsonSerializerOptions _serializerOptions;
2324
private readonly IHybridDateTimeProvider _timeProvider;
2425
private readonly IOptions<CrdtConfig> _crdtConfig;
26+
private readonly ILogger<DataModel> _logger;
2527

2628
//constructor must be internal because CrdtRepository is internal
2729
internal DataModel(CrdtRepository crdtRepository,
2830
JsonSerializerOptions serializerOptions,
2931
IHybridDateTimeProvider timeProvider,
30-
IOptions<CrdtConfig> crdtConfig)
32+
IOptions<CrdtConfig> crdtConfig,
33+
ILogger<DataModel> logger)
3134
{
3235
_crdtRepository = crdtRepository;
3336
_serializerOptions = serializerOptions;
3437
_timeProvider = timeProvider;
3538
_crdtConfig = crdtConfig;
39+
_logger = logger;
3640
}
3741

3842

@@ -131,19 +135,48 @@ private static ChangeEntity<IChange> ToChangeEntity(IChange change, int index)
131135
async Task ISyncable.AddRangeFromSync(IEnumerable<Commit> commits)
132136
{
133137
commits = commits.ToArray();
134-
_timeProvider.TakeLatestTime(commits.Select(c => c.HybridDateTime));
135-
var (oldestChange, newCommits) = await _crdtRepository.FilterExistingCommits(commits.ToArray());
136-
//no changes added
137-
if (oldestChange is null || newCommits is []) return;
138-
139-
await using var transaction = await _crdtRepository.BeginTransactionAsync();
140-
//if there are deferred commits, update snapshots with them first
141-
if (_deferredCommits is not []) await UpdateSnapshotsByDeferredCommits();
142-
//don't save since UpdateSnapshots will also modify newCommits with hashes, so changes will be saved once that's done
143-
await _crdtRepository.AddCommits(newCommits, false);
144-
await UpdateSnapshots(oldestChange, newCommits);
145-
await ValidateCommits();
146-
await transaction.CommitAsync();
138+
try
139+
{
140+
_timeProvider.TakeLatestTime(commits.Select(c => c.HybridDateTime));
141+
var (oldestChange, newCommits) = await _crdtRepository.FilterExistingCommits(commits.ToArray());
142+
//no changes added
143+
if (oldestChange is null || newCommits is []) return;
144+
145+
await using var transaction = await _crdtRepository.BeginTransactionAsync();
146+
//if there are deferred commits, update snapshots with them first
147+
if (_deferredCommits is not []) await UpdateSnapshotsByDeferredCommits();
148+
//don't save since UpdateSnapshots will also modify newCommits with hashes, so changes will be saved once that's done
149+
await _crdtRepository.AddCommits(newCommits, false);
150+
await UpdateSnapshots(oldestChange, newCommits);
151+
await ValidateCommits();
152+
await transaction.CommitAsync();
153+
}
154+
catch (DbUpdateException e)
155+
{
156+
_logger.LogError(e, "Failed to sync commits, check {FailedImportPath} for more details", _crdtConfig.Value.FailedSyncOutputPath);
157+
await DumpFailedSync(new
158+
{
159+
ExceptionMessage = e.ToString(),
160+
Commits = commits.DefaultOrder(),
161+
Objects = e.Entries.Select(entry => entry.Entity)
162+
});
163+
throw;
164+
}
165+
}
166+
167+
private async Task DumpFailedSync(object data)
168+
{
169+
try
170+
{
171+
Directory.CreateDirectory(_crdtConfig.Value.FailedSyncOutputPath);
172+
await using var failedImport =
173+
File.Create(Path.Combine(_crdtConfig.Value.FailedSyncOutputPath, "last-failed-import.json"));
174+
await JsonSerializer.SerializeAsync(failedImport, data, _serializerOptions);
175+
}
176+
catch (Exception e)
177+
{
178+
_logger.LogError(e, "Failed to dump failed import");
179+
}
147180
}
148181

149182
ValueTask<bool> ISyncable.ShouldSync()

0 commit comments

Comments
 (0)