• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

net-daemon / netdaemon / 6662098528

26 Oct 2023 04:22PM UTC coverage: 80.706% (-0.2%) from 80.941%
6662098528

push

github

web-flow
Better handling of faulty unloading of appa (#961)

* handle unloading of faulty apps better and prohibit apps instancing if previous unloading fails

* refactor

* test for slow dispose

808 of 1148 branches covered (0.0%)

Branch coverage included in aggregate %.

26 of 26 new or added lines in 3 files covered. (100.0%)

2944 of 3501 relevant lines covered (84.09%)

50.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.81
/src/AppModel/NetDaemon.AppModel/Internal/Application.cs
1
using NetDaemon.AppModel.Internal.AppFactories;
2

3
namespace NetDaemon.AppModel.Internal;
4

5
internal class Application : IApplication
6
{
7
    private const int MaxTimeInInitializeAsyncInMs = 5000;
8

9
    private readonly IServiceProvider _provider;
10
    private readonly ILogger<Application> _logger;
11
    private readonly IAppFactory _appFactory;
12
    private readonly IAppStateManager? _appStateManager;
13

14
    private bool _isErrorState;
15

16
    public Application(IServiceProvider provider, ILogger<Application> logger, IAppFactory appFactory)
79✔
17
    {
18
        _provider = provider;
79✔
19
        _logger = logger;
79✔
20
        _appFactory = appFactory;
79✔
21

22
        // Can be missing so it is not injected in the constructor
23
        _appStateManager = provider.GetService<IAppStateManager>();
79✔
24
    }
79✔
25

26
    // Used in tests
27
    internal ApplicationContext? ApplicationContext { get; private set; }
447✔
28

29
    public string Id => _appFactory.Id;
215✔
30

31
    public ApplicationState State
32
    {
33
        get
34
        {
35
            if (_isErrorState)
320✔
36
                return ApplicationState.Error;
41✔
37

38
            return ApplicationContext is null ? ApplicationState.Disabled : ApplicationState.Running;
279✔
39
        }
40
    }
41

42
    public async Task SetStateAsync(ApplicationState state)
43
    {
44
        switch (state)
45
        {
46
            case ApplicationState.Enabled:
47
                await LoadApplication(state);
1✔
48
                break;
1✔
49
            case ApplicationState.Disabled:
50
                await UnloadApplication(state);
1✔
51
                break;
1✔
52
            case ApplicationState.Error:
53
                _isErrorState = true;
10✔
54
                await SaveStateIfStateManagerExistAsync(state);
10✔
55
                break;
10✔
56
            case ApplicationState.Running:
57
                throw new ArgumentException("Running state can only be set internally", nameof(state));
1✔
58
        }
59
    }
12✔
60

61
    /// <summary>
62
    ///    Unloads the application and log any errors.If application unload takes longer than 3 seconds it will log a warning and return.
63
    /// </summary>
64
    private async Task UnloadContextAsync()
65
    {
66
        if (ApplicationContext is not null)
7✔
67
        {
68
            try
69
            {
70
               await ApplicationContext.DisposeAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
7✔
71
            }
7✔
72
            catch (Exception e)
×
73
            {
74
                if (e is TimeoutException)
×
75
                    _logger.LogError("Unloading the app '{App}' takes longer than expected, please check for blocking code", Id);
×
76
                else
77
                    _logger.LogError(e, "Unloading the app '{App}' failed", Id);
×
78

79
                ApplicationContext = null;
×
80
                return;
×
81
            }
82
        }
83

84
        ApplicationContext = null;
7✔
85
        _logger.LogDebug("Successfully unloaded app {Id}", Id);
7✔
86
    }
7✔
87

88
    public async ValueTask DisposeAsync()
89
    {
90
        await UnloadContextAsync().ConfigureAwait(false);
6✔
91
    }
6✔
92

93
    private async Task UnloadApplication(ApplicationState state)
94
    {
95
        await UnloadContextAsync().ConfigureAwait(false);
1✔
96

97
        await SaveStateIfStateManagerExistAsync(state).ConfigureAwait(false);
1✔
98
    }
1✔
99

100
    private async Task LoadApplication(ApplicationState state)
101
    {
102
        // first we save state "Enabled", this will also
103
        // end up being state "Running" if instancing is successful
104
        // or "Error" if instancing the app fails
105
        await SaveStateIfStateManagerExistAsync(state);
1✔
106
        if (ApplicationContext is null)
1✔
107
            await InstanceApplication().ConfigureAwait(false);
1✔
108
    }
1✔
109

110
    public async Task InitializeAsync()
111
    {
112
        if (await ShouldInstanceApplicationAsync(Id).ConfigureAwait(false))
78✔
113
            await InstanceApplication().ConfigureAwait(false);
76✔
114
    }
78✔
115

116
    private async Task InstanceApplication()
117
    {
118
        try
119
        {
120
            ApplicationContext = new ApplicationContext(_provider, _appFactory);
77✔
121

122
            // Init async and warn user if taking too long.
123
            var initAsyncTask = ApplicationContext.InitializeAsync();
67✔
124
            var timeoutTask = Task.Delay(MaxTimeInInitializeAsyncInMs);
67✔
125
            await Task.WhenAny(initAsyncTask, timeoutTask).ConfigureAwait(false);
67✔
126
            if (timeoutTask.IsCompleted)
67!
127
                _logger.LogWarning(
×
128
                    "InitializeAsync is taking too long to execute in application {Id}, this function should not be blocking",
×
129
                    Id);
×
130

131
            await initAsyncTask; // Continue to wait even if timeout is set so we do not miss errors
67✔
132

133
            await SaveStateIfStateManagerExistAsync(ApplicationState.Running);
67✔
134
            _logger.LogInformation("Successfully loaded app {Id}", Id);
67✔
135
        }
67✔
136
        catch (Exception e)
10✔
137
        {
138
            _logger.LogError(e, "Error loading app {Id}", Id);
10✔
139
            await SetStateAsync(ApplicationState.Error);
10✔
140
        }
141
    }
77✔
142

143
    private async Task SaveStateIfStateManagerExistAsync(ApplicationState appState)
144
    {
145
        if (_appStateManager is not null)
79✔
146
            await _appStateManager.SaveStateAsync(Id, appState).ConfigureAwait(false);
10✔
147
    }
79✔
148

149
    private async Task<bool> ShouldInstanceApplicationAsync(string id)
150
    {
151
        if (_appStateManager is null)
78✔
152
            return true;
68✔
153
        return await _appStateManager.GetStateAsync(id).ConfigureAwait(false)
10✔
154
               == ApplicationState.Enabled;
10✔
155
    }
78✔
156
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc