chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
//            Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;
 
namespace OpenTap
{
    /// <summary>
    /// Indicates how a IResource property should be handled when being opened or closed.
    /// </summary>
    public enum ResourceOpenBehavior
    {
        /// <summary>
        /// The resources pointed to by this property will be opened in sequence, so any referenced resources are open before Open() and until after Close().
        /// </summary>
        /// <remarks>This is the default behavior</remarks>
        Before,
        /// <summary>
        /// Indicates that a resource property on a resource can be opened in parallel with the resource itself.
        /// </summary>
        InParallel,
        /// <summary>
        /// Do not try to open the resource referenced by this property.
        /// </summary>
        Ignore
    }
 
    /// <summary>
    /// Indicates how a IResource property should be handled when being opened or closed.
    /// By default the resources will be opened in sequence, so any referenced resources are open before Open() and until after Close().
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class ResourceOpenAttribute : Attribute
    {
        /// <summary>
        /// Behavior of how the resource should be handled.
        /// </summary>
        public readonly ResourceOpenBehavior Behavior;
 
        /// <summary>
        /// Creates a new ResourceOpen attribute for a resource property.
        /// </summary>
        /// <param name="behavior"></param>
        public ResourceOpenAttribute(ResourceOpenBehavior behavior)
        {
            this.Behavior = behavior;
        }
    }
 
    /// <summary>
    /// This indicates what stage a testplan execution is at.
    /// </summary>
    public enum TestPlanExecutionStage
    {
        /// <summary>
        /// Indicates that a testplan is being opened.
        /// </summary>
        Open,
        /// <summary>
        /// Indicates that a testplan is starting to execute.
        /// </summary>
        /// <remarks>Implies that the testplan already is open.</remarks>
        Execute,
 
        /// <summary>
        /// Indicates that a teststep is about to run it's PrePlanRun.
        /// </summary>
        PrePlanRun,
        /// <summary>
        /// Indicates that a teststep is about to be run.
        /// </summary>
        Run,
        /// <summary>
        /// Indicates that a teststep is about to run it's PostPlanRun.
        /// </summary>
        PostPlanRun,
    }
 
    /// <summary>
    /// A resource manager implements this interface to be able to control how resources are opened and closed during a testplan execution.
    /// </summary>
    public interface IResourceManager : ITapPlugin
    {
        /// <summary>
        /// This event is triggered when a resource is opened. The event may block in which case the resource will remain open for the entire call.
        /// </summary>
        event Action<IResource> ResourceOpened;
 
        /// <summary>
        /// Get a snapshot of all currently opened resources.
        /// </summary>
        IEnumerable<IResource> Resources { get; }
 
        /// <summary>
        /// Sets the resources that should always be opened when the testplan is.
        /// </summary>
        IEnumerable<IResource> StaticResources { get; set; }
        /// <summary>
        /// This property should be set to all teststeps that are enabled to be run.
        /// </summary>
        List<ITestStep> EnabledSteps { get; set; }
 
        /// <summary>
        /// Waits for all the resources that have been signalled to open to be opened.
        /// </summary>
        /// <param name="cancellationToken">Used to cancel the wait early.</param>
        void WaitUntilAllResourcesOpened(CancellationToken cancellationToken);
 
        /// <summary>
        /// Waits for the specific resources to be open.
        /// </summary>
        /// <param name="cancellationToken">Used to cancel the wait early.</param>
        /// <param name="targets"></param>
        void WaitUntilResourcesOpened(CancellationToken cancellationToken, params IResource[] targets);
 
        /// <summary>
        /// Signals that an action is beginning.
        /// </summary>
        /// <param name="planRun">The planrun for the currently executing testplan.</param>
        /// <param name="item">The item affected by the current action. This can be either a testplan or a teststep.</param>
        /// <param name="stage">The stage that is beginning.</param>
        /// <param name="cancellationToken">Used to cancel the step early.</param>
        void BeginStep(TestPlanRun planRun, ITestStepParent item, TestPlanExecutionStage stage, CancellationToken cancellationToken);
 
        /// <summary>
        /// Signals that an action has completed.
        /// </summary>
        /// <param name="item">The item affected by the current action. This can be either a testplan or a teststep.</param>
        /// <param name="stage">The stage that was just completed.</param>
        void EndStep(ITestStepParent item, TestPlanExecutionStage stage);
    }
 
    /// <summary>
    /// Utility functions shared by <see cref="ResourceTaskManager"/> and <see cref="LazyResourceManager"/>.
    /// </summary>
    internal static class ResourceManagerUtils
    {
        struct EnumerableKey
        {
            readonly int key;
            readonly object[] items;
            public EnumerableKey(object[] items)
            {
                this.items = items;
                key = 3275321;
                foreach (var item in items)
                    key = ((item?.GetHashCode() ?? 0) + key) * 3275321;
            }
 
            public override int GetHashCode() => key;
            public override bool Equals(object obj)
            {
                if (obj is EnumerableKey other)
                    return other.key == key && other.items.SequenceEqual(items);
                return false;
            }
        }
        
        static List<ResourceNode> GetResourceNodesNoCache(object[] source)
        {
            var resources = new ResourceDependencyAnalyzer().GetAllResources(source, out bool analysisError);
            if (analysisError)
            {
                throw new OperationCanceledException("Error while analyzing dependencies between resources.");
            }
            return resources;
        }
 
        class ResourceNodeCache : ICacheOptimizer
        {
            public static readonly ThreadField<Dictionary<EnumerableKey, List<ResourceNode>>> Cache = new ThreadField<Dictionary<EnumerableKey, List<ResourceNode>>>();
            public void LoadCache() => Cache.Value = new Dictionary<EnumerableKey, List<ResourceNode>>();
 
            public void UnloadCache() => Cache.Value = null;
        }
        
        /// <summary>
        /// Gets ResourceNodes for all resources. This includes the ones from <see cref="IResourceManager.EnabledSteps"/> and  <see cref="IResourceManager.StaticResources"/>.
        /// </summary>
        public static List<ResourceNode> GetResourceNodes(IEnumerable<object> _source)
        {
            var source = _source.ToArray();
            var cache = ResourceNodeCache.Cache.Value;
            if (cache != null)
            {
                var key = new EnumerableKey(source);
                lock (cache)
                {
 
                    if (cache.TryGetValue(key, out List<ResourceNode> result))
                        return result;
                    result = GetResourceNodesNoCache(source);
                    cache[key] = result;
                    return result;
                }
            }
 
            return GetResourceNodesNoCache(source);
        }
    }
 
    /// <summary>
    /// Manages the asynchronous opening and closing of <see cref="Resource"/>s in separate threads.
    /// Resources are opened and closed in order depending on dependencies between them.
    /// </summary>
    [Display("Default", Order: 0)]
    internal class ResourceTaskManager : IResourceManager
    {
        /// <summary> Prints a friendly name. </summary>
        public override string ToString() => "Default Resource Manager";
        
        static readonly TraceSource log = Log.CreateSource("Resources");
        
        readonly List<ResourceNode> openedResources = new();
        readonly LockManager lockManager = new();
        
        // open tasks is the main work of opening a resource.
        readonly ConcurrentDictionary<IResource, Task> openTasks = new();
        
        // finally tasks are for notifying that resources has been opened.
        readonly ConcurrentDictionary<IResource, Task> finallyTasks = new();
 
        /// <summary>
        /// This event is triggered when a resource is opened. The event may block in which case the resource will remain open for the entire call.
        /// </summary>
        public event Action<IResource> ResourceOpened;
 
        internal static TraceSource GetLogSource(IResource res)
        {
            return Log.GetOwnedSource(res) ?? Log.CreateSource(res.Name ?? "Resource");
        }
        
        void OpenResource(ResourceNode node, WaitHandle canStart)
        {
            canStart.WaitOne();
            var taskArray = node.StrongDependencies.Select(dep => openTasks[dep]).ToArray();
            Task.WaitAll(taskArray);
 
            var sw = Stopwatch.StartNew();
 
            var resourceLog = GetLogSource(node.Resource);
 
            try
            {
                ResourcePreOpenEvent.Invoke(node.Resource);
                
                node.Resource.Open();
 
                resourceLog.Info(sw, "Resource \"{0}\" opened.", node.Resource);
 
                // when there are no weak dependencies just invoke ResourceOpened directly.
                if (node.WeakDependencies.Count <= 0)
                {
                    ResourceOpened?.Invoke(node.Resource);
                    return;
                }
                
                // ...otherwise, we cannot wait for weak dependencies as part of the 'OpenResource' work.
                // Doing that will cause a dead-lock with circular resource references.
                // ResourceOpened should only be invoked after weak dependencies has been opened mostly for legacy reasons.
                // So we wait for weak deps and call ReosurceOpened in a new worker thread.
                
                var weakDeps = node.WeakDependencies.Select(dep => openTasks[dep]).ToArray();
                
                finallyTasks[node.Resource] = TapThread.StartAwaitable(() =>
                {
                    try
                    {
                        Task.WaitAll(weakDeps);
                        ResourceOpened?.Invoke(node.Resource);
                    }
                    catch (Exception ex)
                    {
                        string msg = $"Error while opening resource \"{node.Resource}\"";
                        throw new ExceptionCustomStackTrace(msg, null, ex);
                    }
                });
 
            }
            catch (Exception ex)
            {
                string msg = $"Error while opening resource \"{node.Resource}\"";
                throw new ExceptionCustomStackTrace(msg, null, ex);
            }
        }
 
        /// <summary> Waits for a specific resource to be open. </summary>
        /// <param name="cancellationToken">Used to cancel the wait early.</param>
        /// <param name="targets">The resources that we wait to be opened.</param>
        public void WaitUntilResourcesOpened(CancellationToken cancellationToken, params IResource[] targets)
        {
            try
            {
                // successfully completed tasks are not worth waiting for.
                // waiting for unsuccessfully completed tasks gives us a useful error.
                
                var waitFor = targets.Select(target => openTasks[target])
                    .Where(task => !task.IsCompleted || task.IsFaulted).ToArray();
                if (waitFor.Length > 0)
                {
                    // WaitAll waits until all finish even if an exception occurs in one task.
                    Task.WaitAll(waitFor, cancellationToken);
                }
                
                var waitForFinallyTasks = targets
                    .Where(finallyTasks.ContainsKey)
                    .Select(target => finallyTasks[target])
                    .Where(task => !task.IsCompleted || task.IsFaulted).ToArray();
                if(waitForFinallyTasks.Length > 0)
                    Task.WaitAll(waitForFinallyTasks, cancellationToken);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception e)
            {
                e.RethrowInner();
            }
        }
 
        /// <summary>
        /// Waits for all the resources to be opened.
        /// </summary>
        /// <param name="cancellationToken">Used to cancel the wait early</param>
        public void WaitUntilAllResourcesOpened(CancellationToken cancellationToken)
        {
            WaitUntilResourcesOpened(cancellationToken, openTasks.Keys.ToArray());
        }
 
        /// <summary>
        /// Get a snapshot of all currently opened resources.
        /// </summary>
        public IEnumerable<IResource> Resources => openTasks.Keys;
 
        /// <summary>
        /// Sets the resources that should always be opened when the test plan is.
        /// </summary>
        public IEnumerable<IResource> StaticResources { get; set; }
        /// <summary>
        /// This property should be set to all test steps that are enabled to be run.
        /// </summary>
        public List<ITestStep> EnabledSteps { get; set; }
 
        /// <summary>
        /// Blocks the thread while closing all resources in parallel.
        /// </summary>
         void CloseAllResources()
        {
            Dictionary<IResource, List<IResource>> dependencies =
                openedResources.ToDictionary(r => r.Resource,
                    r => new List<IResource>()); // this dictionary will hold resources with dependencies (keys) and what resources depend on them (values)
            foreach (var n in openedResources)
            foreach (var dep in n.StrongDependencies)
                dependencies[dep].Add(n.Resource);
 
            Dictionary<IResource, Task> closeTasks = new Dictionary<IResource, Task>();
 
            foreach (var r in openedResources)
                closeTasks[r.Resource] = new Task(o =>
                {
                    ResourceNode res = (ResourceNode) o;
 
                    // Wait for the resource to open to open before closing it.
                    // in rare cases, another instrument failing open will cause close to be called.
                    try
                    {
                        openTasks[res.Resource].Wait();
                    }
                    catch
                    {
 
                    }
 
                    // wait for resources that depend on this resource (res) to close before closing this
                    Task.WaitAll(dependencies[res.Resource].Select(x => closeTasks[x]).ToArray());
                    var resourceLog = GetLogSource(res.Resource);
                    Stopwatch timer = Stopwatch.StartNew();
                    try
                    {
                        res.Resource.Close();
                    }
                    catch (Exception e)
                    {
 
                        log.Error("Error while closing \"{0}\": {1}", res.Resource.Name, e.Message);
                        log.Debug(e);
 
                    }
 
                    if (resourceLog != null)
                        resourceLog.Info(timer, "Resource \"{0}\" closed.", res.Resource);
                }, r);
 
            var closeTaskArray = closeTasks.Values.ToArray();
            closeTaskArray.ForEach(t => t.Start());
 
            void complainAboutWait()
            {
                log.Debug("Waiting for resources to close:");
                foreach (var res in openTasks.Keys)
                {
                    if (res.IsConnected)
                        log.Debug(" - {0}", res);
                }
            }
 
            using (TimeoutOperation.Create(complainAboutWait))
            {
                Task.WaitAll(closeTaskArray);
            }
        }
 
        void BeginOpenResources(List<ResourceNode> resources, CancellationToken cancellationToken)
        {
            lockManager.BeforeOpen(resources, cancellationToken);
 
            {
                // check if any resources that has been deleted from InstrumentSettings or DutSettings
                // are still being referred to.
                var untouched = new IComponentSettingsList[]{InstrumentSettings.Current, 
                        DutSettings.Current, ResultSettings.Current}
                    .SelectMany(x => x.GetRemovedAliveResources())
                    .ToHashSet();
                foreach (var res in resources)
                {
                    if (untouched.Contains(res.Resource))
                    {
                        if(res.Depender != null)
                            throw new Exception($"Deleted resource '{res.Resource}' is in use by {res.Depender.DeclaringType}.");
                        throw new Exception($"Deleted resource '{res.Resource}' is in use.");
                    }
                }
            }
 
            // Check null resources
            if (resources.Any(res => res.Resource == null))
            {
                EnabledSteps.ForEach(step => step.CheckResources());
 
                // Now check resources since we know one of them should have a null resource
                resources.ForEach(res =>
                {
                    if (res.StrongDependencies.Contains(null) || res.WeakDependencies.Contains(null))
                        throw new Exception(String.Format("Resource property not set on resource {0}. Please configure resource.", res.Resource));
                });
            }
            else
            {
                // Open all resources asynchronously
                var wait = new ManualResetEventSlim(false);
                foreach (ResourceNode r in resources)
                {
                    if (openTasks.ContainsKey(r.Resource)) continue;
 
                    openedResources.Add(r);
 
                    // async used to avoid blocking the thread while waiting for tasks.
                    openTasks[r.Resource] = TapThread.StartAwaitable(() => OpenResource(r, wait.WaitHandle));
                }
                wait.Set();
            }
        }
 
        /// <summary>
        /// Signals that an action is beginning.
        /// </summary>
        /// <param name="planRun">The planrun for the currently executing testplan.</param>
        /// <param name="item">The item affected by the current action. This can be either a testplan or a teststep.</param>
        /// <param name="stage">The stage that is beginning.</param>
        /// <param name="cancellationToken">Used to cancel the step early.</param>
        public void BeginStep(TestPlanRun planRun, ITestStepParent item, TestPlanExecutionStage stage, CancellationToken cancellationToken)
        {
            switch (stage)
            {
                case TestPlanExecutionStage.Execute:
                    if (item is TestPlan testPlan)
                    {
                        var resources = ResourceManagerUtils.GetResourceNodes(StaticResources.Cast<object>().Concat(EnabledSteps));
 
                        // Proceed to open resources in case they have been changed or closed since last opening/executing the testplan.
                        // In case any are null, we need to do this before the resource prompt to allow a ILockManager implementation to 
                        // set the resource first.
                        if (resources.Any(r => r.Resource == null))
                            BeginOpenResources(resources, cancellationToken);
 
                        testPlan.StartResourcePromptAsync(planRun, resources.Select(res => res.Resource));
                        
                        if (resources.Any(r => openTasks.ContainsKey(r.Resource) == false))
                            BeginOpenResources(resources, cancellationToken); 
                    }
                    break;
                case TestPlanExecutionStage.Open:
                    if (item is TestPlan)
                    {
                        var resources = ResourceManagerUtils.GetResourceNodes(StaticResources.Cast<object>().Concat(EnabledSteps));
                        BeginOpenResources(resources, cancellationToken);
                    }
                    break;
                case TestPlanExecutionStage.Run:
                case TestPlanExecutionStage.PrePlanRun:
                    {
                        bool openCompletedWithSuccess = openTasks.Values.All(x => x.Status == TaskStatus.RanToCompletion);
                        if (!openCompletedWithSuccess)
                        {   // open did not complete or threw an exception.
 
                            using (TimeoutOperation.Create(() => TestPlan.PrintWaitingMessage(Resources)))
                                WaitUntilAllResourcesOpened(cancellationToken);
                        }
                        break;
                    }
                case TestPlanExecutionStage.PostPlanRun: break;
            }
        }
 
        /// <summary>
        /// Signals that an action has completed.
        /// </summary>
        /// <param name="item">The item affected by the current action. This can be either a testplan or a teststep.</param>
        /// <param name="stage">The stage that was just completed.</param>
        public void EndStep(ITestStepParent item, TestPlanExecutionStage stage)
        {
            switch (stage)
            {
                case TestPlanExecutionStage.Open:
                    if (item is TestPlan)
                    {
                        try
                        {
                            CloseAllResources();
                        }
                        finally
                        {
                            lockManager.AfterClose(openedResources, CancellationToken.None);
                        }
                    }
                    break;
            }
        }
    }
 
    
    /// <summary>
    /// Opens resources in a lazy way only before teststeps or global plan resources actually need them.
    /// </summary>
    [Display("Short Lived Connections", "Opens resources only right before they are needed (e.g. before an individual test step starts). And closes them again immediately after.", Order: 1)]
    internal class LazyResourceManager : IResourceManager
    {
        /// <summary> Prints a friendly name. </summary>
        /// <returns></returns>
        public override string ToString() => "Short Lived Connections";
        
        /// <summary> Manages the state for a single resource. For example an Instrument or Result Listener. </summary>
        class ResourceInfo
        {
            enum ResourceState
            {
                Reset,
                Opening,
                Open,
                Closing
            }
 
            ResourceState state { get; set; } = ResourceState.Reset;
            
            // counts up on open and down on close. When it reaches 0, it can finally be closed. 
            int referenceCount;
 
            // lock used for managing local state. For example reference count.
            readonly object lockObj = new object();
 
            Task openTask = Task.CompletedTask;
            Task closeTask = Task.CompletedTask;
 
            public ResourceNode ResourceNode { get; }
 
            public ResourceInfo(ResourceNode resourceNode)
            {
                ResourceNode = resourceNode;
            }
 
            public Task RequestSteady()
            {
                lock (lockObj)
                    switch(state)
                    {
                        case ResourceState.Opening:
                            return openTask;
                        default:
                            return Task.CompletedTask;
                    }
            }
 
            public bool ShouldBeOpen()
            {
                lock (lockObj)
                    return referenceCount > 0;
            }
 
            void OpenResource(LazyResourceManager requester, CancellationToken cancellationToken)
            {
                var node = ResourceNode;
                foreach (var dep in node.StrongDependencies)
                {
                    if (dep == null) continue;
                    requester.RequestResourceOpen(dep, cancellationToken).Wait(cancellationToken);
                }
 
                var sw = Stopwatch.StartNew();
 
                var resourceLog = ResourceTaskManager.GetLogSource(node.Resource);
 
                try
                {
                    try
                    {
                        ResourcePreOpenEvent.Invoke(node.Resource);
                        
                        node.Resource.Open();
                        resourceLog.Info(sw, "Resource \"{0}\" opened.", node.Resource);
                    }
                    finally
                    {
                        lock (lockObj)
                            if (state == ResourceState.Opening)
                                state = ResourceState.Open;
                    }
 
                    foreach (var dep in node.WeakDependencies)
                    {
                        if (dep == null) continue;
                        requester.RequestResourceOpen(dep, cancellationToken).Wait(cancellationToken);
                    }
                }
                catch (Exception ex)
                {
                    string msg = $"Error while opening resource \"{node.Resource}\"";
                    throw new ExceptionCustomStackTrace(msg, null, ex);
                }
                requester.ResourceOpenedCallback(node.Resource);
            }
 
            public Task RequestOpen(LazyResourceManager requester, CancellationToken cancellationToken)
            {
                lock (lockObj)
                {
                    referenceCount++;
 
                    switch (state)
                    {
                        case ResourceState.Reset:
                            state = ResourceState.Opening;
 
                            return openTask =
                                TapThread.StartAwaitable(() => OpenResource(requester, cancellationToken));
                        case ResourceState.Opening:
                            return openTask;
                        case ResourceState.Open:
                            return Task.CompletedTask;
                        case ResourceState.Closing:
                            return closeTask.ContinueWith(t => RequestOpen(requester, cancellationToken).Wait());
                    }
 
                    return Task.CompletedTask;
                }
            }
 
            public Task RequestClose(LazyResourceManager requester)
            {
                lock (lockObj)
                {
                    referenceCount--;
 
                    if (referenceCount == 0)
                        switch (state)
                        {
                            case ResourceState.Reset:
                            case ResourceState.Closing:
                                throw new Exception("Should never happen");
                            case ResourceState.Opening:
                            case ResourceState.Open:
                                {
                                    state = ResourceState.Closing;
 
                                    return closeTask = TapThread.StartAwaitable(() =>
                                    {
                                        try
                                        {
                                            // wait for the resource to open before close.
                                            requester.resources[ResourceNode.Resource].openTask?.Wait();
                                        }
                                        catch
                                        {
                                            
                                        }
 
                                        Task.WaitAll(ResourceNode.WeakDependencies.Select(requester.RequestResourceClose).ToArray());
                                        var reslog = ResourceTaskManager.GetLogSource(ResourceNode.Resource);
                                        Stopwatch timer = Stopwatch.StartNew();
                                        try
                                        {
                                            ResourceNode.Resource.Close();
                                        }
                                        catch (Exception e)
                                        {
                                            reslog.Error("Error while closing \"{0}\": {1}", ResourceNode.Resource.Name, e.Message);
                                            reslog.Debug(e);
                                        }
                                        reslog.Info(timer, "Resource \"{0}\" closed.", ResourceNode.Resource);
 
                                        Task.WaitAll(ResourceNode.StrongDependencies.Select(requester.RequestResourceClose).ToArray());
 
                                        state = ResourceState.Reset;
                                    });
                                }
                        }
 
                    return Task.CompletedTask;
                }
            }
        }
 
        readonly object resourceLock = new object();
        readonly Dictionary<IResource, ResourceInfo> resources = new Dictionary<IResource, ResourceInfo>();
        readonly Dictionary<ITestStep, List<IResource>> resourceDependencies = new Dictionary<ITestStep, List<IResource>>();
        readonly List<ResourceNode> resourceWithBeforeOpenCalled = new List<ResourceNode>();
        readonly LockManager lockManager = new LockManager();
 
        void OpenResources(List<ResourceNode> toOpen, CancellationToken cancellationToken)
        {
            toOpen.RemoveAll(x => x.Resource == null);
 
            lock (resourceLock)
                foreach (var extra in toOpen)
                    if (!resources.ContainsKey(extra.Resource))
                        resources[extra.Resource] = new ResourceInfo(extra);
 
            try
            {
                Task.WaitAll(toOpen.Select(res => RequestResourceOpen(res.Resource, cancellationToken)).ToArray(), cancellationToken);
            }
            catch(OperationCanceledException)
            {
 
            }
        }
 
        void CloseResources(IEnumerable<IResource> toClose)
        {
            if (toClose.Any())
            {
                Task.WaitAll(toClose.Select(RequestResourceClose).ToArray());
                lock (resourceWithBeforeOpenCalled)
                {
                    var nodes = resourceWithBeforeOpenCalled.Where(rn => toClose.Contains(rn.Resource)).ToArray();
                    lockManager.AfterClose(nodes, CancellationToken.None);
                    foreach (var node in nodes)
                        resourceWithBeforeOpenCalled.Remove(node);
                }
            }
        }
 
        /// <summary>
        /// This property should be set to all test steps that are enabled to be run.
        /// </summary>
        public List<ITestStep> EnabledSteps { get; set; }
 
        /// <summary>
        /// This event is triggered when a resource is opened. The event may block in which case the resource will remain open for the entire call.
        /// </summary>
        public event Action<IResource> ResourceOpened;
 
        /// <summary>
        /// Get a snapshot of all currently opened resources.
        /// </summary>
        public IEnumerable<IResource> Resources
        {
            get
            {
                lock (resourceLock)
                    return resources.Where(x => x.Value.ShouldBeOpen()).Select(x => x.Key).ToList();
            }
        }
 
        /// <summary>
        /// Sets the resources that should always be opened when the testplan is.
        /// </summary>
        public IEnumerable<IResource> StaticResources { get; set; }
 
        /// <summary>
        /// Signals that an action is beginning.
        /// </summary>
        /// <param name="planRun">The planrun for the currently executing testplan.</param>
        /// <param name="item">The item affected by the current action. This can be either a testplan or a teststep.</param>
        /// <param name="stage">The stage that is beginning.</param>
        /// <param name="cancellationToken">Used to cancel the step early.</param>
        public void BeginStep(TestPlanRun planRun, ITestStepParent item, TestPlanExecutionStage stage, CancellationToken cancellationToken)
        {
            switch (stage)
            {
                case TestPlanExecutionStage.Open:
                case TestPlanExecutionStage.Execute:
                    {
                        var resources = ResourceManagerUtils.GetResourceNodes(StaticResources);
 
                        if (item is TestPlan plan && stage == TestPlanExecutionStage.Execute)
                        {
                            // Prompt for metadata for all resources, not only static ones.
                            var testPlanResources = ResourceManagerUtils.GetResourceNodes(EnabledSteps);
                            plan.StartResourcePromptAsync(planRun, resources.Concat(testPlanResources).Select(res => res.Resource));
                        }
 
                        if (resources.All(r => r.Resource?.IsConnected ?? false))
                            return;
 
                        // Call ILockManagers before checking for null
                        try
                        {
                            lockManager.BeforeOpen(resources, cancellationToken);
                        }
                        finally
                        {
                            lock (resourceWithBeforeOpenCalled)
                            {
                                resourceWithBeforeOpenCalled.AddRange(resources);
                            }
                        }
 
                        try
                        {
                            // Check null resources
                            if (resources.Any(res => res.Resource == null))
                            {
                                // Now check resources since we know one of them should have a null resource
                                resources.ForEach(res =>
                                {
                                    if (res.StrongDependencies.Contains(null) || res.WeakDependencies.Contains(null))
                                        throw new Exception(String.Format("Resource property not set on resource {0}. Please configure resource.", res.Resource));
                                });
                            }
                        }
                        finally
                        {
                            OpenResources(resources, cancellationToken);
                        }
                        break;
                    }
 
                case TestPlanExecutionStage.Run:
                    if (item is ITestStep step)
                    {
                        var resources = ResourceManagerUtils.GetResourceNodes(new List<object> { step });
                        if (resources.Any())
                        {
                            // Call ILockManagers before checking for null
                            try
                            {
                                lockManager.BeforeOpen(resources, cancellationToken);
                            }
                            finally
                            {
                                lock (resourceWithBeforeOpenCalled)
                                {
                                    resourceWithBeforeOpenCalled.AddRange(resources);
                                }
                            }
 
                            try
                            {
                                // Check null resources
                                if (resources.Any(res => res.Resource == null))
                                {
                                    step.CheckResources();
 
                                    // Now check resources since we know one of them should have a null resource
                                    resources.ForEach(res =>
                                    {
                                        if (res.StrongDependencies.Contains(null) || res.WeakDependencies.Contains(null))
                                            throw new Exception(String.Format("Resource property not set on resource {0}. Please configure resource.", res.Resource));
                                    });
                                }
                            }
                            finally
                            {
                                lock (resourceLock)
                                {
                                    resourceDependencies[step] = resources.Select(x => x.Resource).ToList();
                                }
 
                                OpenResources(resources, cancellationToken);
                            }
                            WaitHandle.WaitAny(new[] { planRun.PromptWaitHandle, planRun.MainThread.AbortToken.WaitHandle });
                        }
                    }
                    break;
            }
        }
 
        /// <summary>
        /// Signals that an action has completed.
        /// </summary>
        /// <param name="item">The item affected by the current action. This can be either a testplan or a teststep.</param>
        /// <param name="stage">The stage that was just completed.</param>
        public void EndStep(ITestStepParent item, TestPlanExecutionStage stage)
        {
            switch (stage)
            {
                case TestPlanExecutionStage.Open:
                case TestPlanExecutionStage.Execute:
                    lock (resourceLock)
                        CloseResources(Resources);
                    break;
 
                case TestPlanExecutionStage.Run:
                    if (item is ITestStep step)
                    {
                        if (resourceDependencies.TryGetValue(step, out List<IResource> usedResourcesRaw))
                        {
                            var usedResources = usedResourcesRaw.Where(r => r is IResource).ToArray();
                            lock (resourceLock)
                            {
                                resourceDependencies.Remove(step);
                            }
 
                            CloseResources(usedResources);
                        }
                    }
                    break;
            }
        }
 
        private void WaitFor(CancellationToken cancellationToken, IEnumerable<IResource> targets)
        {
            try
            {
                Task[] steadyTasks;
                lock(resourceLock)
                    steadyTasks = targets.Select(x => resources[x].RequestSteady()).ToArray();
 
                Task.WaitAll(steadyTasks, cancellationToken);
            }
            catch (OperationCanceledException)
            {
                // Just exit since the abort exception will be handled elsewhere.
            }
            catch (KeyNotFoundException)
            {
                foreach (var target in targets)
                {
                    if (resources.ContainsKey(target) == false)
                    {
                        throw new ArgumentException($"Resource '{target}' is not used in the test plan.", "targets");
                    }
                }
                throw;
            }
        }
 
        /// <summary>
        /// Waits for all the resources that have been signalled to open to be opened.
        /// </summary>
        /// <param name="cancellationToken">Used to cancel the wait early.</param>
        public void WaitUntilAllResourcesOpened(CancellationToken cancellationToken)
        {
            WaitFor(cancellationToken, Resources);
        }
 
        /// <summary>
        /// Waits for the specific resources to be open.
        /// </summary>
        /// <param name="cancellationToken">Used to cancel the wait early.</param>
        /// <param name="targets"></param>
        public void WaitUntilResourcesOpened(CancellationToken cancellationToken, params IResource[] targets)
        {
            WaitFor(cancellationToken, targets);
        }
 
        Task RequestResourceOpen(IResource resource, CancellationToken cancellationToken)
        {
            lock (resourceLock)
                return resources[resource].RequestOpen(this, cancellationToken);
        }
 
        Task RequestResourceClose(IResource resource)
        {
            lock (resourceLock)
                return resources[resource].RequestClose(this);
        }
 
        void ResourceOpenedCallback(IResource resource)
        {
            ResourceOpened?.Invoke(resource);
        }
    }
}