-
Notifications
You must be signed in to change notification settings - Fork 120
Потокобезопасная регистрация типов #1765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the | |
| ----------------------------------------------------------*/ | ||
|
|
||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using OneScript.Contexts; | ||
|
|
@@ -18,8 +19,12 @@ namespace ScriptEngine.Machine | |
| { | ||
| public class DefaultTypeManager : ITypeManager | ||
| { | ||
| private readonly Dictionary<string, int> _knownTypesIndexes = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase); | ||
| private readonly List<TypeDescriptor> _knownTypes = new List<TypeDescriptor>(); | ||
| // Типы регистрируются и из фоновых заданий (ПодключитьСценарий, внешние компоненты). | ||
| // Регистрация идет под блокировкой, чтение — без нее: словарь конкурентный, | ||
| // а список типов при регистрации заменяется новым массивом. | ||
| private readonly object _registrationLock = new object(); | ||
| private readonly ConcurrentDictionary<string, TypeDescriptor> _knownTypesByName = new ConcurrentDictionary<string, TypeDescriptor>(StringComparer.InvariantCultureIgnoreCase); | ||
| private volatile TypeDescriptor[] _knownTypes = Array.Empty<TypeDescriptor>(); | ||
| private readonly TypeFactoryCache _factoryCache = new TypeFactoryCache(); | ||
| private readonly ILazyTypeResolver[] _resolvers; | ||
|
|
||
|
|
@@ -42,9 +47,9 @@ public DefaultTypeManager(IEnumerable<ILazyTypeResolver> resolvers = null) | |
|
|
||
| public TypeDescriptor GetTypeByName(string name) | ||
| { | ||
| if (_knownTypesIndexes.TryGetValue(name, out var index)) | ||
| if (_knownTypesByName.TryGetValue(name, out var knownType)) | ||
| { | ||
| return _knownTypes[index]; | ||
| return knownType; | ||
| } | ||
|
|
||
| if (TryResolveLazily(name, out var resolvedType)) | ||
|
|
@@ -70,9 +75,8 @@ public bool TryGetType(Type frameworkType, out TypeDescriptor type) | |
|
|
||
| public bool TryGetType(string name, out TypeDescriptor type) | ||
| { | ||
| if (_knownTypesIndexes.TryGetValue(name, out var index)) | ||
| if (_knownTypesByName.TryGetValue(name, out type)) | ||
| { | ||
| type = _knownTypes[index]; | ||
| return true; | ||
| } | ||
|
|
||
|
|
@@ -87,37 +91,38 @@ public bool TryGetType(string name, out TypeDescriptor type) | |
|
|
||
| public TypeDescriptor RegisterType(string name, string alias, Type implementingClass) | ||
| { | ||
| if (_knownTypesIndexes.ContainsKey(name)) | ||
| lock (_registrationLock) | ||
| { | ||
| var td = GetTypeByName(name); | ||
| if (td.ImplementingClass != implementingClass) | ||
| if (_knownTypesByName.TryGetValue(name, out var td)) | ||
| { | ||
| throw new InvalidOperationException($"Name `{name}` is already registered"); | ||
| if (td.ImplementingClass != implementingClass) | ||
| { | ||
| throw new InvalidOperationException($"Name `{name}` is already registered"); | ||
| } | ||
|
|
||
| return td; | ||
| } | ||
|
|
||
| return td; | ||
| } | ||
| else | ||
| { | ||
| var typeDesc = new TypeDescriptor(implementingClass, name, alias); | ||
| RegisterTypeInternal(typeDesc); | ||
| return typeDesc; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| public void RegisterType(TypeDescriptor typeDescriptor) | ||
| { | ||
| if (_knownTypesIndexes.TryGetValue(typeDescriptor.Name, out var index)) | ||
| lock (_registrationLock) | ||
| { | ||
| var knownType = _knownTypes[index]; | ||
| if (knownType != typeDescriptor) | ||
| throw new InvalidOperationException($"Type {typeDescriptor} already registered"); | ||
|
|
||
| return; | ||
| if (_knownTypesByName.TryGetValue(typeDescriptor.Name, out var knownType)) | ||
| { | ||
| if (knownType != typeDescriptor) | ||
| throw new InvalidOperationException($"Type {typeDescriptor} already registered"); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| RegisterTypeInternal(typeDescriptor); | ||
| } | ||
|
|
||
| RegisterTypeInternal(typeDescriptor); | ||
| } | ||
|
|
||
| public ITypeFactory GetFactoryFor(TypeDescriptor type) | ||
|
|
@@ -127,12 +132,16 @@ public ITypeFactory GetFactoryFor(TypeDescriptor type) | |
|
|
||
| private void RegisterTypeInternal(TypeDescriptor td) | ||
| { | ||
| var nextListId = _knownTypes.Count; | ||
| _knownTypesIndexes.Add(td.Name, nextListId); | ||
| // Сначала список: тип, найденный по имени, уже есть и в нем | ||
| var knownTypes = _knownTypes; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Просьба пояснить, зачем делается именно так
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Список типов читают без блокировки: TryGetType(Type), GetTypeByFrameworkType и IsKnownType перебирают его, а RegisteredTypes отдает наружу — Рефлектор.ИзвестныеТипы перебирает его, пока другое задание может подключать сценарий. List.Add параллельно с перебором либо падает с «Collection was modified», либо отдает null: размер увеличивается раньше, чем записан элемент, а при расширении подменяется внутренний массив. Поэтому при регистрации собирается новый массив и подменяется ссылка: читатель один раз берет ссылку и перебирает неизменный снимок. С блокировкой на чтение RegisteredTypes все равно пришлось бы копировать на каждый вызов, а так копирование происходит только при регистрации. Регистрация редкая — на старте около 280 типов плюс подключаемые сценарии, так что копирование незаметно. Массив обновляется раньше словаря имен, чтобы тип, найденный по имени, уже был и в списке. |
||
| var newKnownTypes = new TypeDescriptor[knownTypes.Length + 1]; | ||
| knownTypes.CopyTo(newKnownTypes, 0); | ||
| newKnownTypes[knownTypes.Length] = td; | ||
| _knownTypes = newKnownTypes; | ||
|
|
||
| _knownTypesByName[td.Name] = td; | ||
| if (!string.IsNullOrWhiteSpace(td.Alias) && td.Alias != td.Name) | ||
| _knownTypesIndexes[td.Alias] = nextListId; | ||
|
|
||
| _knownTypes.Add(td); | ||
| _knownTypesByName[td.Alias] = td; | ||
| } | ||
|
|
||
| private bool TryResolveLazily(string name, out TypeDescriptor type) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.