-
Notifications
You must be signed in to change notification settings - Fork 97
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
Explicit assembly loading #850
Conversation
No, the reason why we have everything in the DI is to allow multiple instances of DotVVM (and entire AspNetCore in a process). This approach will lead to collisions and fail horribly if I'd run multiple servers in the process. However, it will be mostly fine to register all allowed assemblies in a single "cache" - have a union of both configurations. |
The name |
I am not sure why it was named that - it is quite old. Basically it maintains a list of assemblies that are used in Roslyn compilation, and caches Roslyn MetadataReferences. |
Running two instances of DotVVM in a single app would be a nice thing, but I'm not sure how many people would even consider doing this. |
startupTracer.TraceEvent(StartupTracingConstants.AddDotvvmStarted); | ||
|
||
var config = DotvvmConfiguration.CreateDefault(s => | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we format the {
on the first line in expressions.
services.Services.AddScoped<IRequestTracer>(s => { | ||
var config = s.GetRequiredService<DotvvmConfiguration>(); | ||
return (config.Debug ? (IRequestTracer)s.GetService<DiagnosticsRequestTracer>() : null) ?? new NullRequestTracer(); | ||
}); | ||
|
||
services.Services.AddScoped<DiagnosticsStartupTracer>(); | ||
services.Services.AddScoped<IStartupTracer>(s => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why Scoped
? It is created once for the app startup anyway, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this - I haven't noticed that this method is meant for the Diagnostic Window feature in VS extension that is not finished and probably doesn't work.
|
||
namespace Microsoft.Extensions.DependencyInjection | ||
{ | ||
public static class ServiceCollectionExtensions | ||
{ | ||
private static readonly DiagnosticsStartupTracer startupTracer = new DiagnosticsStartupTracer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait, where does the information from this tracer go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is added to the IServiceCollection
container later (at the end of the file).
In ASP.NET Core, it needs to be a static field because we need it before the IServiceCollection
is ready and because it is called from both ConfigureServices
and Configure
methods in Startup
.
In OWIN it can be just a local variable as there is only one method for initialization of DotVVM.
@@ -56,7 +57,11 @@ public static DotvvmConfiguration UseDotVVM<TStartup>(this IAppBuilder app, stri | |||
|
|||
private static DotvvmConfiguration UseDotVVM(this IAppBuilder app, string applicationRootPath, bool useErrorPages, bool debug, IDotvvmServiceConfigurator configurator, IDotvvmStartup startup, Func<IServiceCollection, IServiceProvider> serviceProviderFactoryMethod = null, Action<DotvvmConfiguration> modifyConfiguration = null) | |||
{ | |||
var config = DotvvmConfiguration.CreateDefault(s => { | |||
var startupTracer = new DiagnosticsStartupTracer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is super confusing - one tracer is registered in the DI and there is another? Which one should be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is correct, I removed the other one.
{ | ||
if (sentEventsCount < events.Count) | ||
{ | ||
informationSender.SendInformationAsync(BuildDiagnosticsInformation(events.Skip(sentEventsCount).ToList())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we should rather use a loop with await Task.Delay(10sec)
so that we can await this task.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a loop so it would never end. Or maybe I don't understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I don't understand if the timer fires only once or every 10sec. In any case, why not use Task.Delay
in an async method? We could await the task returned from SendInformationAsync
public virtual CSharpCompilation CreateCompilation(string assemblyName) | ||
{ | ||
return CSharpCompilation.Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) | ||
.AddReferences(referencedAssembliesCache.Value.Select(a => assemblyCache.GetAssemblyMetadata(a))); | ||
.AddReferences(assemblyCache.GetAllAssemblies().Select(a => assemblyCache.GetAssemblyMetadata(a))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to reference only some assemblies and now we reference everything when the experimental option is disabled, right? I don't know if that will break, but I'd guess there is a reson why the logic used to be so complex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch - I've distinguished between the referenced assemblies and "all" assemblies, and work only with referenced assemblies here.
@@ -167,7 +169,7 @@ public string EmitCreateObject(string typeName, object[] constructorArguments = | |||
} | |||
|
|||
var typeSyntax = ReflectionUtils.IsFullName(typeName) | |||
? ParseTypeName(ReflectionUtils.FindType(typeName)) | |||
? ParseTypeName(compiledAssemblyCache.FindType(typeName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I see this, the type parsing should not be done by emitter, can we move it to the caller?
@@ -769,7 +771,7 @@ public IEnumerable<SyntaxTree> BuildTree(string namespaceName, string className, | |||
UseType(BuilderDataContextType); | |||
|
|||
var controlType = ReflectionUtils.IsFullName(ResultControlType) | |||
? ParseTypeName(ReflectionUtils.FindType(ResultControlType)) | |||
? ParseTypeName(compiledAssemblyCache.FindType(ResultControlType)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, type parsing should not be done by emitter.
|
||
public void Enable() | ||
{ | ||
ThrowIfFrozen(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is redundant.
@@ -36,7 +37,7 @@ protected virtual IEnumerable<Type> ResolveAllTypesDerivedFromIResource(string d | |||
|
|||
protected virtual IEnumerable<Assembly> GetAllAssembliesLoadedAssemblies() | |||
{ | |||
return ReflectionUtils.GetAllAssemblies(); | |||
return AppDomain.CurrentDomain.GetAssemblies(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to be unused, anyway. I don't know why it's virtual, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed together with ResolveAllTypesDerivedFromIResource
that was unused too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compiler overrides this method for its purposes.
I did. It is what we do in the benchmarks. It is also a convenient way to run two separate apps that are on different domains in a single process (like admin + the actual app). |
Cool, good to know. |
{ | ||
if (sentEventsCount < events.Count) | ||
{ | ||
informationSender.SendInformationAsync(BuildDiagnosticsInformation(events.Skip(sentEventsCount).ToList())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I don't understand if the timer fires only once or every 10sec. In any case, why not use Task.Delay
in an async method? We could await the task returned from SendInformationAsync
I've got rid of the timer completely. When the DotVVM initialization ends, the events are sent using the |
{new string('-', 70 + 15 + 15)} | ||
{messages}"; | ||
} | ||
File.AppendAllText(logFilePath, messages); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may run in parallel, we should have a lock around this line (I know it's just samples, but still)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right - fixed.
} | ||
|
||
public bool IsAssemblyNamespace(string fullName) | ||
=> GetAllNamespaces().Contains(fullName, StringComparer.Ordinal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I know this is just copy paste from the old utils. However, it would make more sense to create the HashSet
once with the Ordinal
comparer, and then cache it in a Lazy
field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we rename the CompiledAssemblyCache
to something else in the next PR, this is ok :)
Since PR #812 contained a lot of changes and was difficult to merge, I've extracted part of the changes as a separate PR.
FindType
,GetAllAssemblies
etc.) were moved fromReflectionUtils
toCompiledAssemblyCache
and changed to be instance methods. The logic inside these methods was not changed.ExplicitAssemblyLoading
which disables aggressive loading of all referenced assemblies (including transitive references) and uses only assemblies explicitly listed inconfig.Markup.Assemblies
.IStartupTracer
for the DotVVM startup code.ReflectionUtils
, there isCompiledAssemblyCache.Instance
which contains a singleton assembly cache. In tests, it is possible that multiple instances of this class are created, which is unfortunate, but tolerable. In real DotVVM web apps, there should be always only instance created by DI.