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

[DirectoryAssemblyResolver] add GetAssembly () that allows to set R… #87

Closed
wants to merge 1 commit into from

Conversation

lewurm
Copy link
Contributor

@lewurm lewurm commented Sep 22, 2016

…eaderParameters and fix Resolve () so that it actually uses parameters

@dnfclas
Copy link

dnfclas commented Sep 22, 2016

Hi @lewurm, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!

The agreement was validated by .NET Foundation and real humans are currently evaluating your PR.

TTYL, DNFBOT;

@@ -187,7 +196,7 @@ public AssemblyDefinition Resolve (AssemblyNameReference reference, ReaderParame
AssemblyDefinition candidate = null;
foreach (var dir in SearchDirectories) {
if ((assemblyFile = SearchDirectory (name, dir)) != null) {
var loaded = Load (assemblyFile);
var loaded = Load (assemblyFile, parameters);
Copy link
Contributor

Choose a reason for hiding this comment

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

@lewurm did you miss adding 'ReaderParameters parameters' on line 147?
also we can make use of
public virtual AssemblyDefinition Load (string fileName, ReaderParameters parameters = null)
so we don't need to duplicate the Load method same for ReadAssembly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, there's already a version with ReaderParameters on line 152.
thanks for the hint about default parameter values, I'll change that.

…eaderParameters and fix `Resolve ()` so that it actually uses parameters
@lewurm lewurm force-pushed the fix-sharing-violation branch from d2b5bc1 to 8fbaeee Compare September 22, 2016 09:45
@jonpryor
Copy link
Member

TL;DR: No.

What I didn't realize is that this PR is related to Bug 44529, which dotnet/android#231 attempts to address.

Background: In the "old times" (Cecil <= 0.9.x), AssemblyDefinition was an in-memory construct. It did not implement IDisposable, and calling code could (more or less) call AssemblyDefinition.ReadAssembly() with impunity, because everything would be kept in memory; there couldn't be any issues with file sharing exceptions.

It was in this world order that DirectoryAssemblyResolver was originally written.

Then came the Cecil 0.10 change, which completely alters these semantics: AssemblyDefinition now implements IDisposable, and also keeps open a file stream. Additionally, if ReaderParameters.ReadWrite is true, the assembly can only be opened once:

var rp = new ReaderParameters () { ReadWrite = true };
var r1 = AssemblyDefinition.ReadAssembly ("Mono.Cecil.dll", rp);
var r2 = AssemblyDefinition.ReadAssembly ("Mono.Cecil.dll", rp);
// System.IO.IOException: Sharing violation on path .../Mono.Cecil.dll

What does DirectoryAssemblyResolver do? It implements IAssemblyResolver, yes, but it also caches all resolved assemblies. DirectoryAssemblyResolver.Resolve()/etc. not only loads the assembly and returns an AssemblyDefinition, but it caches that AssemblyDefinition instance so that future calls to .Resolve()/etc. with the same assembly name return the same instance.

Very important aside: Commit dfed286 is buggy, because DirectoryAssemblyResolver.Dispose(bool) is a no-op. It needs to be corrected to also call AssemblyDefinition.Dispose() on all cached instances.

This caching means that -- by design! -- DirectoryAssemblyResolver will introduce AssemblyDefinition sharing/reuse. Meaning this code would be Bad™:

var r = new DirectoryAssemblyResolver (...);
using (var a = r.Load (filename)) {
    // ...
}
var a2 = r.Load (filename);
// a2 contains a *disposed* AssemblyDefinition instance.

Fortunately, there should be no such code in Java.Interop or xamarin-android, because AssemblyDefinition didn't previously implement IDisposable, so it wouldn't be possible to do using (var a = r.Load(filename)).

Which allows me to breath slightly easier. (Oh the panic attack before I realized that...)

Let us now return to the IOException and DirectoryAssemblyResolver. We should not provide any DirectoryAssemblyResolver methods which take ReaderParameters as a parameter. Why not? Because DirectoryAssemblyResolver caches the AssemblyDefinitions, and if different AssemblyDefinitions had different associated ReaderParameters, I'm not even sure how to begin reasoning about the resulting code:

var r = new DirectoryAssemblyResolver (...);
r.Load ("Mono.Cecil.dll");
r.GetAssembly ("Mono.Cecil.dll", new ReaderParameters {...});
// ReaderParameters *ignored*; previously loaded `Mono.Cecil.dll` returned

This is particularly difficult, considering that IAssemblyResolver -- and thus DirectoryAssemblyResolver -- is implicitly used when trying to resolve types, e.g. in the linker/etc. It's very easy to get very confused.


Proposal:

In order to not go crazy, I believe the following needs to happen:

  1. DirectoryAssemblyResolver.Dispose(bool) needs to be fixed so that all cached AssemblyDefinition instances are disposed of:

    protected virtual void Dispose (bool disposing)
    {
        if (!disposing || cache == null)
            return;
        foreach (var e in cache) {
            e.Value.Dispose ();
        }
        cache = null;
    }
    
  2. The DirectoryAssemblyResolver constructor should be updated to accept a "template" ReaderParameters instance. The semantics would thus be that all (almost all?) DirectoryAssemblyResolver.Load()/etc. would use the ReaderParameters instance provided to the constructor.

    Because DirectoryAssemblyResolver.Resolve() uses Load(), and Resolve() is implicitly used during e.g. type resolution, this would allow controlling all assembly resolution to e.g. be read+write, instead of read-only (the default).

  3. All use of DirectoryAssemblyResolver, everywhere, needs to be audited to ensure:

    1. That the DirectoryAssemblyResolver instance is Dispose()d of.
    2. That use of Load()/Resolve()/GetAssembly() is consistent with the caching semantics of DirectoryAssemblyResolver, e.g. no calling GetAssembly("Foo").Dispose().

jonpryor added a commit to jonpryor/java.interop that referenced this pull request Sep 23, 2016
Context: dotnet#87 (comment)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529

(Private) Bug #44529 is an `IOException` on `xamarin-android/master`
due to file sharing:

	Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
	  at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0
	  at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0
	  at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0
	  at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0

The underlying cause of this change is the migration to
Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes --
has some *semantic* changes [^0].

In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was
entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition`
isn't; it is backed by a `System.IO.Stream`, which can be in-memory
(if `ReaderParameters.InMemory` is `true`), or a `FileStream`
(the default).

This normally might not be bad, except we also have
`Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches*
all created `AssemblyDefinition` instances.

Thus, "normal" *correct* Cecil use would be fine, so long as you know
all assemblies which have been loaded, load them with the correct
`ReaderParameters`, and promptly `Dispose()` of the
`AssemblyDefinition` when done.

`DirectoryAssemblyResolver` throws a wrench in that, because
(1) commit dfed286 incorrectly implemented
`DirectoryAssemblyResolver.Dispose()`, leaving all of the cached
`AssemblyDefinition` instances still "live", which means
(2) The lifetime of the `Stream` underlying the `AssemblyDefinition`
is controlled by the GC, which can mean nearly anything.

This is all a *huge* recipe for confusion.

Fix `DirectoryAssemblyResolver.Dispose()` so that the cached
`AssemblyDefinition` instances are `Dispose()`d of, and review all use
of `DirectoryAssemblyResolver` within Java.Interop to ensure that any
created instances are appropriate `Dispose()`d of.

Additionally, add a new `DirectoryAssemblyResolver` constructor to
allow controlling the `ReaderParameters` that
`DirectoryAssemblyResolver.Load()` uses when loading an assembly:

	partial class DirectoryAssemblyResolver {
		public DirectoryAssemblyResolver (
			Action<string, object[]>  logWarnings,
			bool                      loadDebugSymbols,
			ReaderParameters          loadReaderParameters = null);
	}

The new `loadReaderParameters` allows specifying the default
`ReaderParameters` values to use when
`DirectoryAssemblyResolver.Load()` is invoked. This ensures that all
assemblies loaded by `DirectoryAssemblyResolver` are loaded in a
consistent fashion (e.g. readonly, read+write, in-memory), which will
hopefully allow code to be sanely reasoned about.

[^0]: The best kind of changes!
@jonpryor
Copy link
Member

Superseded by RP #88.

@jonpryor jonpryor closed this Sep 23, 2016
jonpryor added a commit to jonpryor/java.interop that referenced this pull request Sep 23, 2016
Context: dotnet#87 (comment)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529

(Private) Bug #44529 is an `IOException` on `xamarin-android/master`
due to file sharing:

	Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
	  at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0
	  at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0
	  at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0
	  at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0

The underlying cause of this change is the migration to
Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes --
has some *semantic* changes [^0].

In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was
entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition`
isn't; it is backed by a `System.IO.Stream`, which can be in-memory
(if `ReaderParameters.InMemory` is `true`), or a `FileStream`
(the default).

This normally might not be bad, except we also have
`Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches*
all created `AssemblyDefinition` instances.

Thus, "normal" *correct* Cecil use would be fine, so long as you know
all assemblies which have been loaded, load them with the correct
`ReaderParameters`, and promptly `Dispose()` of the
`AssemblyDefinition` when done.

`DirectoryAssemblyResolver` throws a wrench in that, because
(1) commit dfed286 incorrectly implemented
`DirectoryAssemblyResolver.Dispose()`, leaving all of the cached
`AssemblyDefinition` instances still "live", which means
(2) The lifetime of the `Stream` underlying the `AssemblyDefinition`
is controlled by the GC, which can mean nearly anything.

This is all a *huge* recipe for confusion.

Fix `DirectoryAssemblyResolver.Dispose()` so that the cached
`AssemblyDefinition` instances are `Dispose()`d of, and review all use
of `DirectoryAssemblyResolver` within Java.Interop to ensure that any
created instances are appropriate `Dispose()`d of.

Additionally, add a new `DirectoryAssemblyResolver` constructor to
allow controlling the `ReaderParameters` that
`DirectoryAssemblyResolver.Load()` uses when loading an assembly:

	partial class DirectoryAssemblyResolver {
		public DirectoryAssemblyResolver (
			Action<string, object[]>  logWarnings,
			bool                      loadDebugSymbols,
			ReaderParameters          loadReaderParameters = null);
	}

The new `loadReaderParameters` allows specifying the default
`ReaderParameters` values to use when
`DirectoryAssemblyResolver.Load()` is invoked. This ensures that all
assemblies loaded by `DirectoryAssemblyResolver` are loaded in a
consistent fashion (e.g. readonly, read+write, in-memory), which will
hopefully allow code to be sanely reasoned about.

[^0]: The best kind of changes!
jonpryor added a commit to jonpryor/java.interop that referenced this pull request Sep 23, 2016
Context: dotnet#87 (comment)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529

(Private) Bug #44529 is an `IOException` on `xamarin-android/master`
due to file sharing:

	Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
	  at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0
	  at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0
	  at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0
	  at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0

The underlying cause of this change is the migration to
Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes --
has some *semantic* changes [^0].

In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was
entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition`
isn't; it is backed by a `System.IO.Stream`, which can be in-memory
(if `ReaderParameters.InMemory` is `true`), or a `FileStream`
(the default).

This normally might not be bad, except we also have
`Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches*
all created `AssemblyDefinition` instances.

Thus, "normal" *correct* Cecil use would be fine, so long as you know
all assemblies which have been loaded, load them with the correct
`ReaderParameters`, and promptly `Dispose()` of the
`AssemblyDefinition` when done.

`DirectoryAssemblyResolver` throws a wrench in that, because
(1) commit dfed286 incorrectly implemented
`DirectoryAssemblyResolver.Dispose()`, leaving all of the cached
`AssemblyDefinition` instances still "live", which means
(2) The lifetime of the `Stream` underlying the `AssemblyDefinition`
is controlled by the GC, which can mean nearly anything.

This is all a *huge* recipe for confusion.

Fix `DirectoryAssemblyResolver.Dispose()` so that the cached
`AssemblyDefinition` instances are `Dispose()`d of, and review all use
of `DirectoryAssemblyResolver` within Java.Interop to ensure that any
created instances are appropriate `Dispose()`d of.

Additionally, add a new `DirectoryAssemblyResolver` constructor to
allow controlling the `ReaderParameters` that
`DirectoryAssemblyResolver.Load()` uses when loading an assembly:

	partial class DirectoryAssemblyResolver {
		public DirectoryAssemblyResolver (
			Action<string, object[]>  logWarnings,
			bool                      loadDebugSymbols,
			ReaderParameters          loadReaderParameters = null);
	}

The new `loadReaderParameters` allows specifying the default
`ReaderParameters` values to use when
`DirectoryAssemblyResolver.Load()` is invoked. This ensures that all
assemblies loaded by `DirectoryAssemblyResolver` are loaded in a
consistent fashion (e.g. readonly, read+write, in-memory), which will
hopefully allow code to be sanely reasoned about.

[^0]: The best kind of changes!
jonpryor added a commit to jonpryor/java.interop that referenced this pull request Sep 23, 2016
Context: dotnet#87 (comment)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529

(Private) Bug #44529 is an `IOException` on `xamarin-android/master`
due to file sharing:

	Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
	  at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0
	  at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0
	  at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0
	  at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0

The underlying cause of this change is the migration to
Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes --
has some *semantic* changes [^0].

In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was
entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition`
isn't; it is backed by a `System.IO.Stream`, which can be in-memory
(if `ReaderParameters.InMemory` is `true`), or a `FileStream`
(the default).

This normally might not be bad, except we also have
`Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches*
all created `AssemblyDefinition` instances.

Thus, "normal" *correct* Cecil use would be fine, so long as you know
all assemblies which have been loaded, load them with the correct
`ReaderParameters`, and promptly `Dispose()` of the
`AssemblyDefinition` when done.

`DirectoryAssemblyResolver` throws a wrench in that, because
(1) commit dfed286 incorrectly implemented
`DirectoryAssemblyResolver.Dispose()`, leaving all of the cached
`AssemblyDefinition` instances still "live", which means
(2) The lifetime of the `Stream` underlying the `AssemblyDefinition`
is controlled by the GC, which can mean nearly anything.

This is all a *huge* recipe for confusion.

Fix `DirectoryAssemblyResolver.Dispose()` so that the cached
`AssemblyDefinition` instances are `Dispose()`d of, and review all use
of `DirectoryAssemblyResolver` within Java.Interop to ensure that any
created instances are appropriate `Dispose()`d of.

Additionally, add a new `DirectoryAssemblyResolver` constructor to
allow controlling the `ReaderParameters` that
`DirectoryAssemblyResolver.Load()` uses when loading an assembly:

	partial class DirectoryAssemblyResolver {
		public DirectoryAssemblyResolver (
			Action<string, object[]>  logWarnings,
			bool                      loadDebugSymbols,
			ReaderParameters          loadReaderParameters = null);
	}

The new `loadReaderParameters` allows specifying the default
`ReaderParameters` values to use when
`DirectoryAssemblyResolver.Load()` is invoked. This ensures that all
assemblies loaded by `DirectoryAssemblyResolver` are loaded in a
consistent fashion (e.g. readonly, read+write, in-memory), which will
hopefully allow code to be sanely reasoned about.

[^0]: The best kind of changes!
jonpryor added a commit to jonpryor/java.interop that referenced this pull request Sep 23, 2016
Context: dotnet#87 (comment)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529

(Private) Bug #44529 is an `IOException` on `xamarin-android/master`
due to file sharing:

	Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
	  at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0
	  at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0
	  at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0
	  at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0

The underlying cause of this change is the migration to
Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes --
has some *semantic* changes [^0].

In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was
entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition`
isn't; it is backed by a `System.IO.Stream`, which can be in-memory
(if `ReaderParameters.InMemory` is `true`), or a `FileStream`
(the default).

This normally might not be bad, except we also have
`Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches*
all created `AssemblyDefinition` instances.

Thus, "normal" *correct* Cecil use would be fine, so long as you know
all assemblies which have been loaded, load them with the correct
`ReaderParameters`, and promptly `Dispose()` of the
`AssemblyDefinition` when done.

`DirectoryAssemblyResolver` throws a wrench in that, because
(1) commit dfed286 incorrectly implemented
`DirectoryAssemblyResolver.Dispose()`, leaving all of the cached
`AssemblyDefinition` instances still "live", which means
(2) The lifetime of the `Stream` underlying the `AssemblyDefinition`
is controlled by the GC, which can mean nearly anything.

This is all a *huge* recipe for confusion.

Fix `DirectoryAssemblyResolver.Dispose()` so that the cached
`AssemblyDefinition` instances are `Dispose()`d of, and review all use
of `DirectoryAssemblyResolver` within Java.Interop to ensure that any
created instances are appropriate `Dispose()`d of.

Additionally, add a new `DirectoryAssemblyResolver` constructor to
allow controlling the `ReaderParameters` that
`DirectoryAssemblyResolver.Load()` uses when loading an assembly:

	partial class DirectoryAssemblyResolver {
		public DirectoryAssemblyResolver (
			Action<string, object[]>  logWarnings,
			bool                      loadDebugSymbols,
			ReaderParameters          loadReaderParameters = null);
	}

The new `loadReaderParameters` allows specifying the default
`ReaderParameters` values to use when
`DirectoryAssemblyResolver.Load()` is invoked. This ensures that all
assemblies loaded by `DirectoryAssemblyResolver` are loaded in a
consistent fashion (e.g. readonly, read+write, in-memory), which will
hopefully allow code to be sanely reasoned about.

[^0]: The best kind of changes!
atsushieno pushed a commit that referenced this pull request Sep 26, 2016
Context: #87 (comment)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529

(Private) Bug #44529 is an `IOException` on `xamarin-android/master`
due to file sharing:

	Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
	  at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0
	  at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0
	  at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0
	  at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0

The underlying cause of this change is the migration to
Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes --
has some *semantic* changes [^0].

In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was
entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition`
isn't; it is backed by a `System.IO.Stream`, which can be in-memory
(if `ReaderParameters.InMemory` is `true`), or a `FileStream`
(the default).

This normally might not be bad, except we also have
`Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches*
all created `AssemblyDefinition` instances.

Thus, "normal" *correct* Cecil use would be fine, so long as you know
all assemblies which have been loaded, load them with the correct
`ReaderParameters`, and promptly `Dispose()` of the
`AssemblyDefinition` when done.

`DirectoryAssemblyResolver` throws a wrench in that, because
(1) commit dfed286 incorrectly implemented
`DirectoryAssemblyResolver.Dispose()`, leaving all of the cached
`AssemblyDefinition` instances still "live", which means
(2) The lifetime of the `Stream` underlying the `AssemblyDefinition`
is controlled by the GC, which can mean nearly anything.

This is all a *huge* recipe for confusion.

Fix `DirectoryAssemblyResolver.Dispose()` so that the cached
`AssemblyDefinition` instances are `Dispose()`d of, and review all use
of `DirectoryAssemblyResolver` within Java.Interop to ensure that any
created instances are appropriate `Dispose()`d of.

Additionally, add a new `DirectoryAssemblyResolver` constructor to
allow controlling the `ReaderParameters` that
`DirectoryAssemblyResolver.Load()` uses when loading an assembly:

	partial class DirectoryAssemblyResolver {
		public DirectoryAssemblyResolver (
			Action<string, object[]>  logWarnings,
			bool                      loadDebugSymbols,
			ReaderParameters          loadReaderParameters = null);
	}

The new `loadReaderParameters` allows specifying the default
`ReaderParameters` values to use when
`DirectoryAssemblyResolver.Load()` is invoked. This ensures that all
assemblies loaded by `DirectoryAssemblyResolver` are loaded in a
consistent fashion (e.g. readonly, read+write, in-memory), which will
hopefully allow code to be sanely reasoned about.

[^0]: The best kind of changes!
dellis1972 pushed a commit that referenced this pull request Oct 18, 2016
Context: #87 (comment)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=44529

(Private) Bug #44529 is an `IOException` on `xamarin-android/master`
due to file sharing:

	Error executing task StripEmbeddedLibraries: System.IO.IOException: Sharing violation on path .../obj/Release/linksrc/Xamarin.Android.NUnitLite.dll
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x0025f] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in <253a3790b2c44512bbca8669ecfc1822>:0
	  at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
	  at Mono.Cecil.ModuleDefinition.GetFileStream (System.String fileName, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00007] in :0
	  at Mono.Cecil.ModuleDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00007] in :0
	  at Mono.Cecil.AssemblyDefinition.Write (System.String fileName, Mono.Cecil.WriterParameters parameters) [0x00001] in :0
	  at Xamarin.Android.Tasks.StripEmbeddedLibraries.Execute () [0x0034a] in <3d5202a5d4874a76a99388021bf1ab1a>:0

The underlying cause of this change is the migration to
Cecil 0.10.0-beta1-v2 (dfed286), which -- along with API changes --
has some *semantic* changes [^0].

In particular, within Cecil <= 0.9.x, `AssemblyDefinition` was
entirely in-memory. Starting with Cecil 0.10.x, `AssemblyDefinition`
isn't; it is backed by a `System.IO.Stream`, which can be in-memory
(if `ReaderParameters.InMemory` is `true`), or a `FileStream`
(the default).

This normally might not be bad, except we also have
`Java.Interop.Tools.Cecil.DirectoryAssemblyResolver`, which *caches*
all created `AssemblyDefinition` instances.

Thus, "normal" *correct* Cecil use would be fine, so long as you know
all assemblies which have been loaded, load them with the correct
`ReaderParameters`, and promptly `Dispose()` of the
`AssemblyDefinition` when done.

`DirectoryAssemblyResolver` throws a wrench in that, because
(1) commit dfed286 incorrectly implemented
`DirectoryAssemblyResolver.Dispose()`, leaving all of the cached
`AssemblyDefinition` instances still "live", which means
(2) The lifetime of the `Stream` underlying the `AssemblyDefinition`
is controlled by the GC, which can mean nearly anything.

This is all a *huge* recipe for confusion.

Fix `DirectoryAssemblyResolver.Dispose()` so that the cached
`AssemblyDefinition` instances are `Dispose()`d of, and review all use
of `DirectoryAssemblyResolver` within Java.Interop to ensure that any
created instances are appropriate `Dispose()`d of.

Additionally, add a new `DirectoryAssemblyResolver` constructor to
allow controlling the `ReaderParameters` that
`DirectoryAssemblyResolver.Load()` uses when loading an assembly:

	partial class DirectoryAssemblyResolver {
		public DirectoryAssemblyResolver (
			Action<string, object[]>  logWarnings,
			bool                      loadDebugSymbols,
			ReaderParameters          loadReaderParameters = null);
	}

The new `loadReaderParameters` allows specifying the default
`ReaderParameters` values to use when
`DirectoryAssemblyResolver.Load()` is invoked. This ensures that all
assemblies loaded by `DirectoryAssemblyResolver` are loaded in a
consistent fashion (e.g. readonly, read+write, in-memory), which will
hopefully allow code to be sanely reasoned about.

[^0]: The best kind of changes!
jonpryor added a commit to jonpryor/java.interop that referenced this pull request Jun 4, 2020
Changes: dotnet/android-tools@f5fcb9f...3974fc3

  * dotnet/android-tools@3974fc3: [Xamarin.Android.Tools.AndroidSdk] JdkInfo + JDK11 + Windows (dotnet#88)
  * dotnet/android-tools@5552b07: [Xamarin.Android.Tools.AndroidSdk] Improve utility of JDK warnings (dotnet#87)
  * dotnet/android-tools@13cc497: [Xamarin.Android.Tools.AndroidSdk] Prefer JAVA_HOME (dotnet#86)
  * dotnet/android-tools@967c278: Delete NuGet.Config
  * dotnet/android-tools@2d3690e: [Xamarin.Android.Tools.AndroidSdk] Nullable Reference Type support (dotnet#84)
  * dotnet/android-tools@2020b20: [Xamarin.Android.Tools.AndroidSdk] Preview Build-Tools are Last (dotnet#85)
jonpryor added a commit that referenced this pull request Jun 4, 2020
Changes: dotnet/android-tools@f5fcb9f...3974fc3

  * dotnet/android-tools@3974fc3: [Xamarin.Android.Tools.AndroidSdk] JdkInfo + JDK11 + Windows (#88)
  * dotnet/android-tools@5552b07: [Xamarin.Android.Tools.AndroidSdk] Improve utility of JDK warnings (#87)
  * dotnet/android-tools@13cc497: [Xamarin.Android.Tools.AndroidSdk] Prefer JAVA_HOME (#86)
  * dotnet/android-tools@967c278: Delete NuGet.Config
  * dotnet/android-tools@2d3690e: [Xamarin.Android.Tools.AndroidSdk] Nullable Reference Type support (#84)
  * dotnet/android-tools@2020b20: [Xamarin.Android.Tools.AndroidSdk] Preview Build-Tools are Last (#85)
jonpryor added a commit that referenced this pull request Jun 11, 2020
Changes: dotnet/android-tools@23c4fe0...017078f

  * dotnet/android-tools@017078f: [Xamarin.Android.Tools.AndroidSdk] JdkInfo + JDK11 + Windows (#88)
  * dotnet/android-tools@852e4a3: [Xamarin.Android.Tools.AndroidSdk] Improve utility of JDK warnings (#87)
  * dotnet/android-tools@b055edf: [Xamarin.Android.Tools.AndroidSdk] Prefer JI_JAVA_HOME (#86)
  * dotnet/android-tools@ef31658: [Xamarin.Android.Tools.AndroidSdk] Nullable Reference Type support (#84)
  * dotnet/android-tools@b8efdae: [Xamarin.Android.Tools.AndroidSdk] Preview Build-Tools are Last (#85)
@github-actions github-actions bot locked and limited conversation to collaborators Apr 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants