Skip to content
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

Load Assembly referenced from within a lambda closure #135

Merged
merged 34 commits into from
Jul 10, 2019

Conversation

suhsteve
Copy link
Member

@suhsteve suhsteve commented Jun 11, 2019

Please see example in issue #120

The assembly containing the type used in the lambda closure is not loaded as part of the Worker's application domain. When the Worker deserializes the object within the lambda, the type must be loaded from an external Assembly.

This issue seems to be isolated to dotnet core. Using .NET framework, the assembly automatically loaded.

Assembly asm;
try
{
asm = Assembly.LoadFrom(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you checked out https://docs.microsoft.com/en-us/dotnet/framework/deployment/best-practices-for-assembly-loading? I don't think Assembly.LoadFrom is recommended anymore. But instead, using AssemblyLoadContext.

/cc @stephentoub

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion and finding the article. For now we will create issue #138 and revisit the assembly loading in another PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt I investigated this a bit more. The AssemblyLoadContext relies is only available for netcore and not net framework. Ideally we would want to do something like the msbuild example, using #if preprocessor directives and choosing to use Assembly.LoadFrom or AssemblyLoadContext depending on the framework. However since Microsoft.Spark library is targeting netstandard2.0 , i'm not sure how to go about doing this. Would you have any suggestions on how we can do this ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are 2 possibilities:

  1. Detect at runtime if we are running on .NET Framework - which you can use https://source.dot.net/#CoreFx.Private.TestUtilities/System/PlatformDetection.cs,24.
  2. Multi-target Microsoft.Spark to build for both net45 and netcoreapp2.1. But we would still need to target netstandard2.0 as well so other netstandard libraries could reference Spark. This would involve more work and build time, since we would be compiling the assembly 3 times.

My suggestion would be to try (1) above, and fallback to (2) if necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt thanks for your suggestions.

I've tried (1) and while it compiles, it fails at runtime, where it tries to load the incompatible library. I suspect that the net461 Microsoft.Spark.Worker attempts to load it because Microsoft.Spark.dll still contains references to the library.

(2) worked, but we run into the concerns you brought up regarding building separate libraries targeting multiple frameworks. What do you think about the following approach?

Add an AssemblyLoader.cs class to Microsoft.Spark (netstandard2.0) that looks like the following:

    internal static class AssemblyLoader
    {
        internal static Func<string, Assembly> Loader { get; set; } = Assembly.Load;

        internal static Assembly LoadAssembly(string manifestModuleName)
        {
            var sep = Path.DirectorySeparatorChar;
            try
            {
                return Loader(
                    $"{Directory.GetCurrentDirectory()}{sep}{manifestModuleName}");
            }
            catch (FileNotFoundException)
            {
                return Loader(
                    $"{AppDomain.CurrentDomain.BaseDirectory}{sep}{manifestModuleName}");
            }
        }

In Microsoft.Spark.Worker, which targets (net461 and netcoreapp2.1), we can add #if preprocessor directives and set the above AssemblyLoader.Loader property to use AssemblyLoadContext if the target framework is netcore.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am loading the assembly by way of the delegate mentioned previously. This seemed like the cleanest approach.

@suhsteve
Copy link
Member Author

suhsteve commented Jun 14, 2019

This seems to be related to https://github.com/dotnet/coreclr/issues/11498 . Will explore more when addressing #138 .

@imback82 imback82 added the enhancement New feature or request label Jun 15, 2019
@imback82 imback82 added this to the June 2019 milestone Jun 15, 2019
@imback82
Copy link
Contributor

@suhsteve what will be the effort to do #138? Since both this PR and #138 are breaking changes, it will be nice to have this in one release.

@imback82
Copy link
Contributor

@suhsteve what will be the effort to do #138? Since both this PR and #138 are breaking changes, it will be nice to have this in one release.

Let's release this one first and come back to #138.

src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.Worker/Program.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
@@ -212,7 +212,7 @@ private sealed class UdfWrapperData

foreach (UdfSerDe.FieldData field in fields)
{
SerializeUdfs((Delegate)field.Value, curNode, udfWrapperNodes, udfs);
SerializeUdfs((Delegate)field.ValueData?.Value, curNode, udfWrapperNodes, udfs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ValueData is always non-null. Should we either assert or field.ValueData.Value? Otherwise, we will get null exception down the stream, so it may be a bit confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValueData can potentially be null. I updated the UdfSerDeTests with an example.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which test is covering this? Can ValueData be null in this context? Note that it's a delegate in this context (not capture). Can you check comments in line 70 in this file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you're right. My tests do not touch CommandSerde.cs . Looks like it only gets to this case when the current node is a non-UDF, a MapUdfWrapper or WorkerChainHelper. I'll revert this change.

src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark.UnitTest/UdfSerDeTests.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
src/csharp/Microsoft.Spark/Utils/UdfSerDe.cs Outdated Show resolved Hide resolved
Copy link
Contributor

@imback82 imback82 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants