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

Sholtee / injector / 2270

06 Dec 2024 06:22AM UTC coverage: 91.153% (+0.4%) from 90.719%
2270

push

appveyor

Sholtee
seems net5.0 is not supported by AppVeyor anymore

1937 of 2125 relevant lines covered (91.15%)

4.52 hits per line

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

94.44
/SRC/Injector/Private/Scopes/Injector.cs
1
/********************************************************************************
2
* Injector.cs                                                                   *
3
*                                                                               *
4
* Author: Denes Solti                                                           *
5
********************************************************************************/
6
using System;
7
using System.Collections.Generic;
8
using System.Runtime.CompilerServices;
9
using System.Threading;
10
using System.Threading.Tasks;
11

12
using static System.Diagnostics.Debug;
13

14
namespace Solti.Utils.DI.Internals
15
{
16
    using Interfaces;
17
    using Primitives.Patterns;
18
    using Properties;
19

20
    //                                        !!!ATTENTION!!!
21
    //
22
    // This class is a critical component therefore every modification should be done carefully, with
23
    // performance in mind.
24
    // - NO System.Linq
25
    // - NO System.Reflection
26
    // - After ANY modifications, run the unit & performance tests to verify there is no regression
27
    //
28

29
    internal class Injector: Disposable, IScopeFactory, IServiceActivator
30
    {
31
        #region Private
32
        //
33
        // IServiceResolver is thread safe
34
        //
35

36
        private readonly IServiceResolver FServiceResolver;
37

38
        private readonly Injector? FSuper;
39

40
        private readonly object? FInstantiationLock;
41

42
        //
43
        // These fields are always accessed in a write lock.
44
        //
45

46
        private CaptureDisposable? FDisposableStore;  // Not required when there is no disposable service requested
47

48
        private ServicePath? FPath; // Not required when AOT building is enabled 
49

50
        //
51
        // "volatile" is required as slots can be accessed parallelly in the root scope
52
        // (a thread may read FSlots while the other may try to lengthen it).
53
        //
54

55
        private volatile object?[] FSlots;
56

57
        private CaptureDisposable DisposableStore
58
        {
59
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
60
            get => FDisposableStore ??= new CaptureDisposable();
5✔
61
        }
62

63
        private ServicePath Path
64
        {
65
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
66
            get => FPath ??= new ServicePath();
5✔
67
        }
68

69
        private Injector(IServiceResolver resolver, ScopeOptions options, object? tag, object? instantiationLock)
5✔
70
        {
5✔
71
            FServiceResolver = resolver;
5✔
72
            FSlots = Array<object>.Create(resolver.Slots);
5✔
73
            FInstantiationLock = instantiationLock;
5✔
74
            Options = options;
5✔
75
            Tag = tag;
5✔
76
        }
5✔
77

78
#if DEBUG
79
        internal virtual
80
#else
81
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
82
        private
83
#endif
84
        object CreateInstance(AbstractServiceEntry requested)
85
        {
5✔
86
            Assert(requested is not null, "The requested service cannot be NULL");
5✔
87

88
            object? instance, disposable;
89

90
            if (Options.StrictDI)
5✔
91
            {
5✔
92
                AbstractServiceEntry? requestor = FPath?.Last;
5✔
93
                
94
                //
95
                // At the root of the dependency graph this validation makes no sense.
96
                //
97

98
                if (requestor?.State.HasFlag(ServiceEntryStates.Validated) is false)
5✔
99
                    ServiceErrors.EnsureNotBreaksTheRuleOfStrictDI(requestor, requested!, Options.SupportsServiceProvider);
5✔
100
            }
5✔
101

102
            Assert(requested!.State.HasFlag(ServiceEntryStates.Built), "The requested service must be built");
5✔
103

104
            if (!requested.State.HasFlag(ServiceEntryStates.Validated))
5✔
105
            {
5✔
106
                //
107
                // Validate the graph
108
                //
109

110
                Path.Push(requested);
5✔
111
                try
112
                {
5✔
113
                    instance = requested.CreateInstance!(this, out disposable);
5✔
114
                }
5✔
115
                finally
116
                {
5✔
117
                    Path.Pop();
5✔
118
                }
5✔
119

120
                requested.UpdateState(ServiceEntryStates.Validated);
5✔
121
            }
5✔
122
            else
123
                instance = requested.CreateInstance!(this, out disposable);
5✔
124

125
            if (!requested.State.HasFlag(ServiceEntryStates.Instantiated))
5✔
126
            {
5✔
127
                //
128
                // Check the returned insance (only once to improve performance).
129
                //
130

131
                if (instance is null)
5✔
132
                    throw new InvalidOperationException(string.Format(Resources.Culture, Resources.IS_NULL, nameof(instance)));
5✔
133

134
                if (!requested.Type.IsInstanceOfType(instance))  // according to perf tests this check is quite slow
5✔
135
                    throw new InvalidOperationException(string.Format(Resources.Culture, Resources.INVALID_CAST, requested.Type));
5✔
136

137
                ServiceEntryStates newState = ServiceEntryStates.Instantiated;
5✔
138

139
                //
140
                // Set the "Collected" state here, at once with "Instantiated" to not deceive instantiation shortcut
141
                // in GetOrCreateInstance()
142
                //
143

144
                if (disposable is not null)
5✔
145
                    newState |= ServiceEntryStates.Collected;
5✔
146

147
                requested.UpdateState(newState);
5✔
148
            }
5✔
149

150
            if (disposable is not null)
5✔
151
                DisposableStore.Capture(disposable);
5✔
152

153
            return instance;
5✔
154
        }
5✔
155
        #endregion
156

157
        protected static IServiceCollection RegisterBuiltInServices(IServiceCollection services)
158
        {
5✔
159
            ServiceOptions suppressDispose = ServiceOptions.Default with { DisposalMode = ServiceDisposalMode.Suppress };
5✔
160

161
            return services
5✔
162
                .Factory(typeof(IInjector), static (i, _) => i, Lifetime.Scoped, suppressDispose)
5✔
163
                .Factory(typeof(IServiceActivator), static (i, _) => i, Lifetime.Scoped, suppressDispose)
5✔
164
                .Factory(typeof(IScopeFactory), static (i, _) => i, Lifetime.Singleton, suppressDispose) // create SF from the root only
5✔
165
                .Factory(typeof(IServiceResolver), static (i, _) => ((Injector) i).FServiceResolver, Lifetime.Singleton, suppressDispose)
5✔
166
                .Service(typeof(IEnumerable<>), typeof(ServiceEnumerator<>), Lifetime.Scoped)
5✔
167
#if DEBUG
5✔
168
                .Factory(typeof(IReadOnlyCollection<object>), "captured_disposables", static (i, _) => ((Injector) i).DisposableStore.CapturedDisposables, Lifetime.Scoped, suppressDispose)
5✔
169
#endif
5✔
170
                ;
5✔
171
        }
5✔
172

173
        #region IServiceActivator
174
        public object GetOrCreateInstance(AbstractServiceEntry requested)
175
        {
5✔
176
            CheckNotDisposed();
5✔
177

178
            if (requested is null)
5✔
179
                throw new ArgumentNullException(nameof(requested));
5✔
180

181
            //
182
            // Shared entries are resolved from the root scope.
183
            //
184

185
            ServiceEntryFeatures features = requested.Features;
5✔
186
            if (features.HasFlag(ServiceEntryFeatures.Shared) && FSuper is not null)
5✔
187
                return FSuper.GetOrCreateInstance(requested);
5✔
188

189
            //
190
            // Although the value of FSlots might be chnaged by another thread while we are doing
191
            // this check, it won't cause any issues as the new value must contain the same items
192
            // in the same order.
193
            // "slot" might be greater than "FSlots.Length" if we request a scoped service that
194
            // needs to be specialized run-time.
195
            //
196

197
            int slot = requested.AssignedSlot;
5✔
198
            if (features.HasFlag(ServiceEntryFeatures.CreateSingleInstance) && slot < FSlots.Length && FSlots[slot] is not null)
5✔
199
                return FSlots[slot]!;
5✔
200

201
            //
202
            // If the requested service had already been instantiated and it's not a disposable means 
203
            // no lock required.
204
            // Lock required to:
205
            //   - Set the belonging slot as an atomic operation
206
            //   - Build the service path
207
            //   - Extend the disposable store
208
            //
209

210
            ServiceEntryStates state = requested.State;
5✔
211
            if (!features.HasFlag(ServiceEntryFeatures.CreateSingleInstance) && state.HasFlag(ServiceEntryStates.Instantiated) && !state.HasFlag(ServiceEntryStates.Collected))
5✔
212
                return requested.CreateInstance!(this, out _);
5✔
213

214
            //
215
            // Entering to lock is a quite expensive operation (regardless the lock was held or not)
216
            // so try to enter only once
217
            // (according to perf tests Monitor.IsEntered() much faster than Monitor.TryEnter())
218
            //
219

220
            bool lockTaken = false;
5✔
221
            if (FInstantiationLock is not null && !Monitor.IsEntered(FInstantiationLock))
5✔
222
            {
5✔
223
                Monitor.TryEnter(FInstantiationLock, Options.ResolutionLockTimeout, ref lockTaken);
5✔
224
                if (!lockTaken)  // We must grab the lock here
5✔
225
                    throw new TimeoutException();
3✔
226
            }
5✔
227
            try
228
            {
5✔
229
                if (!features.HasFlag(ServiceEntryFeatures.CreateSingleInstance))
5✔
230
                    return CreateInstance(requested);
5✔
231

232
                if (slot >= FSlots.Length)
5✔
233
                    //
234
                    // We reach here when we made a service request that triggered a ServiceResolver update.
235
                    // Scopes created after the update won't be affected as they allocate their slot array
236
                    // with the proper size.
237
                    //
238

239
                    FSlots = Array<object?>.Resize(FSlots, FServiceResolver.Slots);
5✔
240

241
                //
242
                // "??" is required as another thread may have done this work while we reached here.
243
                //
244

245
                return FSlots[slot] ??= CreateInstance(requested);
5✔
246
            }
247
            finally
248
            {
5✔
249
                if (lockTaken)
5✔
250
                    Monitor.Exit(FInstantiationLock);
5✔
251
            }
5✔
252
        }
5✔
253

254
        public IServiceActivator? Super => FSuper;
×
255
        #endregion
256

257
        #region IInjector
258
        public ScopeOptions Options { get; }
259

260
        public virtual object Get(Type type, object? key)
261
        {
5✔
262
            object? instance = TryGet(type, key);
5✔
263
            if (instance is null)
5✔
264
                //
265
                // Cannot provide requestor (FPath.Last) here as Injector.Get() may be called from
266
                // different threads parallelly.
267
                //
268

269
                ServiceErrors.NotFound(type, key, requestor: null);
×
270

271
            return instance!;
5✔
272
        }
5✔
273

274
        public object? TryGet(Type type, object? key)
275
        {
5✔
276
            CheckNotDisposed();
5✔
277

278
            //
279
            // Do NOT examine the interface deeper here as it has performance costs and we don't want
280
            // to pay it on each requests.
281
            //
282

283
            if (type is null)
5✔
284
                throw new ArgumentNullException(nameof(type));
5✔
285

286
            AbstractServiceEntry? entry = FServiceResolver.Resolve(type, key);
5✔
287
            if (entry is null)
5✔
288
                return null;
5✔
289

290
            if (!entry.State.HasFlag(ServiceEntryStates.Built))
5✔
291
            {
×
292
                //
293
                // Since the entry is supposed to be built here, something must be wrong with it
294
                //
295

296
                if (type.IsGenericTypeDefinition)
×
297
                    throw new ArgumentException(Resources.PARAMETER_IS_GENERIC, nameof(type));
×
298

299
                Fail("Entry must be built");
×
300
            }
×
301

302
            return GetOrCreateInstance(entry);
5✔
303
        }
5✔
304
        #endregion
305

306
        #region Dispose
307
        protected override void Dispose(bool disposeManaged)
308
        {
5✔
309
            FDisposableStore?.Dispose();
5✔
310
            base.Dispose(disposeManaged);
5✔
311
        }
5✔
312

313
        protected override ValueTask AsyncDispose() => FDisposableStore?.DisposeAsync() ?? default;
×
314
        #endregion
315

316
        #region IScopeFactory
317
        public virtual IInjector CreateScope(object? tag)
318
        {
5✔
319
            CheckNotDisposed();
5✔
320
            return new Injector(this, tag);
5✔
321
        }
5✔
322
        #endregion
323

324
        #region IHasTag
325
        public object? Tag { get; }
326
        #endregion
327

328
        public Injector(IServiceResolver resolver, ScopeOptions options, object? tag): this
5✔
329
        (
5✔
330
            resolver,
5✔
331
            options,
5✔
332
            tag,
5✔
333
            instantiationLock: new object() // lock required in the root scope only
5✔
334
        ) {}
5✔
335

336
        public Injector(IServiceCollection services, ScopeOptions options, object? tag): this
5✔
337
        (
5✔
338
            new ServiceResolver
5✔
339
            (
5✔
340
                RegisterBuiltInServices(services),
5✔
341
                options
5✔
342
            ),
5✔
343
            options,
5✔
344
            tag
5✔
345
        )
5✔
346
        {
5✔
347
            services.MakeReadOnly();
5✔
348
        }
5✔
349

350
        public Injector(Injector super, object? tag): this(super.FServiceResolver, super.Options, tag, instantiationLock: null)
5✔
351
            => FSuper = super;
5✔
352

353
        public IServiceResolver ServiceResolver => FServiceResolver;
5✔
354
    }
355
}
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