Skip to content

Commit

Permalink
More cleanup and fix save state over restart
Browse files Browse the repository at this point in the history
  • Loading branch information
helto4real committed Dec 27, 2020
1 parent 84f36d6 commit a69a21d
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 23 deletions.
26 changes: 20 additions & 6 deletions src/App/NetDaemon.App/Common/NetDaemonAppBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,19 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase

private readonly Channel<bool> _updateRuntimeInfoChannel =
Channel.CreateBounded<bool>(5);
private readonly CancellationTokenSource _cancelSource = new();
private readonly CancellationTokenSource _cancelSource;
private bool _isDisposed;
private Task? _lazyStoreStateTask;

/// <summary>
/// Constructor
/// </summary>
protected NetDaemonAppBase()
{
_cancelSource = new();
_isDisposed = false;
}

/// <summary>
/// Dependencies on other applications that will be initialized before this app
/// </summary>
Expand Down Expand Up @@ -123,7 +133,7 @@ public async Task RestoreAppStateAsync()
{
_ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");

var obj = await Daemon!.GetDataAsync<IDictionary<string, object?>>(GetUniqueIdForStorage()).ConfigureAwait(false);
var obj = await Daemon.GetDataAsync<IDictionary<string, object?>>(GetUniqueIdForStorage()).ConfigureAwait(false);

if (obj != null)
{
Expand All @@ -143,7 +153,6 @@ public async Task RestoreAppStateAsync()
dynamic serviceData = new FluentExpandoObject();
serviceData.entity_id = EntityId;
await Daemon.SetStateAsync(EntityId, "off").ConfigureAwait(false);
await Daemon.CallServiceAsync("switch", "turn_off", serviceData).ConfigureAwait(false);
}
return;
}
Expand All @@ -155,7 +164,6 @@ public async Task RestoreAppStateAsync()
dynamic serviceData = new FluentExpandoObject();
serviceData.entity_id = EntityId;
await Daemon.SetStateAsync(EntityId, "on").ConfigureAwait(false);
await Daemon.CallServiceAsync("switch", "turn_on", serviceData).ConfigureAwait(false);
}
return;
}
Expand Down Expand Up @@ -189,7 +197,7 @@ public void SaveAppState()
/// <inheritdoc/>
public void Speak(string entityId, string message)
{
_ = Daemon as INetDaemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
_ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
Daemon!.Speak(entityId, message);
}

Expand Down Expand Up @@ -260,13 +268,19 @@ private async Task HandleLazyStorage()
/// </summary>
public async virtual ValueTask DisposeAsync()
{
lock (_cancelSource)
{
if (_isDisposed)
return;
_isDisposed = true;
}
_cancelSource.Cancel();
if (_manageRuntimeInformationUpdatesTask is not null)
await _manageRuntimeInformationUpdatesTask.ConfigureAwait(false);

DaemonCallBacksForServiceCalls.Clear();

this.IsEnabled = false;
IsEnabled = false;
_lazyStoreStateTask = null;
InternalStorageObject = null;
_cancelSource.Dispose();
Expand Down
18 changes: 15 additions & 3 deletions src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,20 @@ namespace NetDaemon.Common.Reactive
/// </summary>
public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive
{
private readonly CancellationTokenSource _cancelTimers = new();
private readonly CancellationTokenSource _cancelTimers;
private EventObservable? _eventObservables;
private StateChangeObservable? _stateObservables;
private bool _isDisposed;

/// <summary>
/// Default constructor
/// </summary>
protected NetDaemonRxApp()
{
_cancelTimers = new();
StateAllChanges = new ReactiveState(this);
EventChanges = new ReactiveEvent(this);
_isDisposed = false;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -70,17 +73,26 @@ public void CallService(string domain, string service, dynamic? data)
/// </summary>
public async override ValueTask DisposeAsync()
{
lock (_cancelTimers)
{
if (_isDisposed)
return;
_isDisposed = true;
}

LogDebug("RxApp {app} is being Disposes", Id!);
// To end timers
_cancelTimers.Cancel();
_cancelTimers?.Cancel();

if (_eventObservables is not null)
_eventObservables!.Clear();

if (_stateObservables is not null)
_stateObservables!.Clear();

_cancelTimers?.Dispose();

await base.DisposeAsync().ConfigureAwait(false);
_cancelTimers.Dispose();
LogDebug("RxApp {app} is Disposed", Id!);
}

Expand Down
1 change: 0 additions & 1 deletion src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public interface IInstanceDaemonApp
/// <summary>
/// Returns a list of instanced daemonapps
/// </summary>
/// <returns></returns>
IEnumerable<INetDaemonAppBase> InstanceDaemonApps();
}
}
50 changes: 38 additions & 12 deletions src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
internal ConcurrentDictionary<string, INetDaemonAppBase> InternalAllAppInstances { get; } = new();

private readonly Scheduler _scheduler;
private bool _isDisposed;

// Following token source and token are set at RUN
private CancellationToken _cancelToken;
Expand All @@ -93,7 +94,6 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
/// <param name="repository">Repository to use</param>
/// <param name="loggerFactory">The loggerfactory</param>
/// <param name="httpHandler">Http handler to use</param>
/// <param name="appInstanceManager">Handles instances of apps</param>
public NetDaemonHost(
IHassClient? hassClient,
IDataRepository? repository,
Expand All @@ -108,6 +108,7 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
?? throw new ArgumentNullException(nameof(hassClient));
_scheduler = new Scheduler(loggerFactory: loggerFactory);
_repository = repository;
_isDisposed = false;
Logger.LogTrace("Instance NetDaemonHost");
}

Expand Down Expand Up @@ -210,11 +211,19 @@ public ICamera Cameras(INetDaemonApp app, Func<IEntityProperties, bool> func)

public async ValueTask DisposeAsync()
{
lock (_cancelDaemon)
{
if (_isDisposed)
return;
_isDisposed = true;
}

_cancelDaemon.Cancel();
await Stop().ConfigureAwait(false);
_cancelDaemon.Dispose();
await _scheduler.DisposeAsync().ConfigureAwait(false);
_cancelDaemon.Dispose();
_cancelTokenSource?.Dispose();

Logger.LogTrace("Instance NetDaemonHost Disposed");
}

Expand Down Expand Up @@ -269,8 +278,7 @@ public IEntity Entity(INetDaemonApp app, params string[] entityId)
public async Task<T?> GetDataAsync<T>(string id) where T : class
{
_cancelToken.ThrowIfCancellationRequested();

_ = _repository as IDataRepository ??
_ = _repository ??
throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!");

if (DataCache.ContainsKey(id))
Expand Down Expand Up @@ -615,6 +623,14 @@ public async Task Stop()
await _scheduler.Stop().ConfigureAwait(false);

InternalState.Clear();
InternalAllAppInstances.Clear();
InternalRunningAppInstances.Clear();
_hassAreas.Clear();
_hassDevices.Clear();
_hassEntities.Clear();
_daemonServiceCallFunctions.Clear();
_externalEventCallSubscribers.Clear();
_eventHandlerTasks.Clear();

Connected = false;
await _hassClient.CloseAsync().ConfigureAwait(false);
Expand All @@ -628,11 +644,20 @@ public async Task Stop()
}

/// <inheritdoc/>
[SuppressMessage("", "1031")]
public async Task UnloadAllApps()
{
Logger.LogTrace("Unloading all apps ({instances}, {running})", InternalAllAppInstances.Count, InternalRunningAppInstances.Count);
foreach (var app in InternalAllAppInstances)
{
await app.Value.DisposeAsync().ConfigureAwait(false);
try
{
await app.Value.DisposeAsync().ConfigureAwait(false);
}
catch (Exception e)
{
Logger.LogError(e, "Failed to unload apps, {app_id}", app.Value.Id);
}
}
InternalAllAppInstances.Clear();
InternalRunningAppInstances.Clear();
Expand Down Expand Up @@ -1214,15 +1239,18 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke
private async Task LoadAllApps()
{
_ = _appInstanceManager ?? throw new NetDaemonNullReferenceException(nameof(_appInstanceManager));

Logger.LogTrace("Loading all apps ({instances}, {running})", InternalAllAppInstances.Count, InternalRunningAppInstances.Count);
// First unload any apps running
await UnloadAllApps().ConfigureAwait(false);

// Get all instances
var instancedApps = _appInstanceManager.InstanceDaemonApps();

if (!InternalRunningAppInstances.IsEmpty)
throw new NetDaemonException("Did not expect running instances!");
{
Logger.LogWarning("Old apps not unloaded correctly. {nr} apps still loaded.", InternalRunningAppInstances.Count);
InternalRunningAppInstances.Clear();
}

foreach (INetDaemonAppBase appInstance in instancedApps!)
{
Expand Down Expand Up @@ -1278,7 +1306,7 @@ private void RegisterAppSwitchesAndTheirStates()
else
await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false);
}
catch (System.Exception e)
catch (Exception e)
{
Logger.LogWarning(e, "Failed to set state from netdaemon switch");
}
Expand Down Expand Up @@ -1309,9 +1337,7 @@ private async Task SetDependentState(string entityId, string state)
if (state == "off")
{
// We need to turn off any dependent apps
var depApps = InternalAllAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id));

foreach (var depApp in depApps)
foreach (var depApp in InternalAllAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id)))
{
await SetDependentState(depApp.EntityId, state).ConfigureAwait(false);
}
Expand Down Expand Up @@ -1356,7 +1382,7 @@ private async Task PersistAppStateAsync(NetDaemonAppBase app)
new Dictionary<string, object?>();

obj["__IsDisabled"] = !app.IsEnabled;
await SaveDataAsync<IDictionary<string, object?>>(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false);
await SaveDataAsync(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false);
}

[SuppressMessage("", "CA1031")]
Expand Down
8 changes: 7 additions & 1 deletion src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class Scheduler : IScheduler
/// Used to cancel all running tasks
/// </summary>
private CancellationTokenSource _cancelSource = new();

private bool _isStopped;
private readonly ConcurrentDictionary<int, Task> _scheduledTasks
= new();

Expand Down Expand Up @@ -274,6 +274,11 @@ private async Task InternalRunInAsync(TimeSpan timeSpan, Func<Task> func, Cancel
/// </summary>
public async Task Stop()
{
if (_isStopped)
return;

_isStopped = true;

_cancelSource.Cancel();

// Make sure we are waiting for the scheduler task as well
Expand All @@ -298,6 +303,7 @@ public async Task Stop()
public async Task Restart()
{
await Stop().ConfigureAwait(false);
_isStopped = false;
_cancelSource = new CancellationTokenSource();
}

Expand Down

0 comments on commit a69a21d

Please sign in to comment.