Fix the crash on changing network settings, and apply resynced contacts in batches
ApplyNetworkSettings disposed the station link without clearing it, so ApplyStationLinkSettings disposed it a second time and threw. Only the second function touches the link now, and StationLink.Dispose can be called twice. Contacts from the network are queued and applied together on the window thread. Each one used to rescore the whole log and redraw the log window, which kept the window thread busy for minutes during a resync. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Avalonia.Threading;
|
||||
using Nonemm.App.Configuration;
|
||||
using Nonemm.Contests;
|
||||
@@ -363,9 +364,7 @@ public sealed class AppSession : IDisposable
|
||||
/// multi-operator entry, following what the settings now say.
|
||||
public void ApplyNetworkSettings()
|
||||
{
|
||||
announcing?.Stop();
|
||||
network?.Dispose();
|
||||
link?.Dispose();
|
||||
network = null;
|
||||
if (!Settings.NetworkEnabled)
|
||||
{
|
||||
@@ -378,8 +377,7 @@ public sealed class AppSession : IDisposable
|
||||
Settings.NetworkPeers);
|
||||
// the socket thread must not touch the log: every other change to it is
|
||||
// made where the windows read it
|
||||
network.UpdateArrived += (_, update) =>
|
||||
Dispatcher.UIThread.Post(() => TakeFromNetwork(update));
|
||||
network.UpdateArrived += (_, update) => QueueFromNetwork(update);
|
||||
network.Start();
|
||||
Changed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
@@ -395,6 +393,7 @@ public sealed class AppSession : IDisposable
|
||||
|
||||
public void ApplyStationLinkSettings()
|
||||
{
|
||||
announcing?.Stop();
|
||||
link?.Dispose();
|
||||
link = null;
|
||||
if (!Settings.StationLinkEnabled)
|
||||
@@ -412,8 +411,7 @@ public sealed class AppSession : IDisposable
|
||||
};
|
||||
// the socket thread must not touch the log: every other change to it is
|
||||
// made where the windows read it
|
||||
link.UpdateArrived += (_, update) =>
|
||||
Dispatcher.UIThread.Post(() => TakeFromNetwork(update));
|
||||
link.UpdateArrived += (_, update) => QueueFromNetwork(update);
|
||||
// the resync reads the log rather than being handed it, and another
|
||||
// station can ask for a rescore when its resync is done
|
||||
link.Log = new SessionContactLog(this);
|
||||
@@ -607,34 +605,75 @@ public sealed class AppSession : IDisposable
|
||||
host.Length > 1 && int.TryParse(host[1], out int given) ? given : Settings.StationLinkPort);
|
||||
}
|
||||
|
||||
/// What another station did to its log, applied to ours. Nothing goes back
|
||||
/// out to the network, and the contact is scored here from the rules
|
||||
/// instead of trusting what the sender put in the message.
|
||||
private void TakeFromNetwork(ContactUpdate update)
|
||||
/// Updates that have arrived from the network and not yet been applied.
|
||||
/// They are applied together on the window thread, so a resync that sends
|
||||
/// a few hundred contacts rescores the log and redraws the windows once
|
||||
/// per batch rather than once per contact.
|
||||
private readonly ConcurrentQueue<ContactUpdate> fromNetwork = new();
|
||||
|
||||
private int networkDrainPosted;
|
||||
|
||||
private void QueueFromNetwork(ContactUpdate update)
|
||||
{
|
||||
fromNetwork.Enqueue(update);
|
||||
if (Interlocked.Exchange(ref networkDrainPosted, 1) == 0)
|
||||
{
|
||||
Dispatcher.UIThread.Post(TakeQueuedFromNetwork);
|
||||
}
|
||||
}
|
||||
|
||||
private void TakeQueuedFromNetwork()
|
||||
{
|
||||
// cleared first: an update that arrives while this runs posts a new drain
|
||||
Interlocked.Exchange(ref networkDrainPosted, 0);
|
||||
List<ContactUpdate> batch = [];
|
||||
while (fromNetwork.TryDequeue(out ContactUpdate? update))
|
||||
{
|
||||
batch.Add(update);
|
||||
}
|
||||
TakeFromNetwork(batch);
|
||||
}
|
||||
|
||||
/// What other stations did to their logs, applied to ours in the order it
|
||||
/// arrived. Nothing goes back out to the network, and the contacts are
|
||||
/// scored here from the rules instead of trusting what the sender put in
|
||||
/// the message.
|
||||
private void TakeFromNetwork(IReadOnlyList<ContactUpdate> updates)
|
||||
{
|
||||
if (Logging is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Settings.LogQsosAtAllStations && update is not ContactsCleared)
|
||||
List<Qso> logged = [];
|
||||
foreach (ContactUpdate update in updates)
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (update)
|
||||
{
|
||||
case ContactLogged logged:
|
||||
Logging.AddFromNetwork(logged.Qso);
|
||||
break;
|
||||
case ContactReplaced replaced:
|
||||
Logging.ReplaceFromNetwork(replaced.Qso, replaced.OldCall, replaced.OldTimestampUtc);
|
||||
break;
|
||||
case ContactDeleted deleted:
|
||||
Logging.DeleteFromNetwork(deleted.Id, deleted.Call, deleted.TimestampUtc);
|
||||
break;
|
||||
case ContactsCleared cleared:
|
||||
Logging.ClearFromNetwork(cleared.StationName, cleared.FromUtc, cleared.Length);
|
||||
break;
|
||||
if (!Settings.LogQsosAtAllStations && update is not ContactsCleared)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (update is ContactLogged contact)
|
||||
{
|
||||
logged.Add(contact.Qso);
|
||||
continue;
|
||||
}
|
||||
// a run of new contacts is applied before the update that follows
|
||||
// it, because that update may be a clear or an edit of one of them
|
||||
Logging.AddRangeFromNetwork(logged);
|
||||
logged.Clear();
|
||||
switch (update)
|
||||
{
|
||||
case ContactReplaced replaced:
|
||||
Logging.ReplaceFromNetwork(replaced.Qso, replaced.OldCall, replaced.OldTimestampUtc);
|
||||
break;
|
||||
case ContactDeleted deleted:
|
||||
Logging.DeleteFromNetwork(deleted.Id, deleted.Call, deleted.TimestampUtc);
|
||||
break;
|
||||
case ContactsCleared cleared:
|
||||
Logging.ClearFromNetwork(cleared.StationName, cleared.FromUtc, cleared.Length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Logging.AddRangeFromNetwork(logged);
|
||||
}
|
||||
|
||||
/// Starts, restarts or stops the keyer, following what the settings say.
|
||||
|
||||
Reference in New Issue
Block a user