Skip to content

Commit e84bbfc

Browse files
jonpryordellis1972
authored andcommitted
[Java.Interop.Tools.Cecil] Fix DirectoryAssemblyResolver.Dispose() (#88)
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!
1 parent b259a81 commit e84bbfc

File tree

4 files changed

+55
-9
lines changed

4 files changed

+55
-9
lines changed

src/Java.Interop.Tools.Cecil/Java.Interop.Tools.Cecil/DirectoryAssemblyResolver.cs

+38-3
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,39 @@ public class DirectoryAssemblyResolver : IAssemblyResolver {
6464
bool loadDebugSymbols;
6565
Action<string, object[]> logWarnings;
6666

67-
public DirectoryAssemblyResolver (Action<string, object[]> logWarnings, bool loadDebugSymbols)
67+
ReaderParameters loadReaderParameters;
68+
69+
static readonly ReaderParameters DefaultLoadReaderParameters = new ReaderParameters {
70+
};
71+
72+
public DirectoryAssemblyResolver (Action<string, object[]> logWarnings, bool loadDebugSymbols, ReaderParameters loadReaderParameters = null)
6873
{
6974
if (logWarnings == null)
7075
throw new ArgumentNullException (nameof (logWarnings));
7176
cache = new Dictionary<string, AssemblyDefinition> ();
7277
this.loadDebugSymbols = loadDebugSymbols;
7378
this.logWarnings = logWarnings;
7479
SearchDirectories = new List<string> ();
80+
this.loadReaderParameters = loadReaderParameters ?? DefaultLoadReaderParameters;
81+
}
82+
83+
public void Dispose ()
84+
{
85+
Dispose (disposing: false);
86+
GC.SuppressFinalize (this);
87+
}
88+
89+
protected virtual void Dispose (bool disposing)
90+
{
91+
if (!disposing || cache == null)
92+
return;
93+
foreach (var e in cache) {
94+
e.Value.Dispose ();
95+
}
96+
cache = null;
7597
}
7698

99+
[Obsolete ("Should not be used; was required with previous Cecil versions.")]
77100
public IDictionary ToResolverCache ()
78101
{
79102
var resolver_cache = new Hashtable ();
@@ -104,9 +127,21 @@ public virtual AssemblyDefinition Load (string fileName)
104127

105128
protected virtual AssemblyDefinition ReadAssembly (string file)
106129
{
130+
bool haveDebugSymbols = loadDebugSymbols &&
131+
(File.Exists (file + ".mdb") ||
132+
File.Exists (Path.ChangeExtension (file, ".pdb")));
107133
var reader_parameters = new ReaderParameters () {
108-
AssemblyResolver = this,
109-
ReadSymbols = loadDebugSymbols && (File.Exists(file + ".mdb") || File.Exists(Path.ChangeExtension(file, ".pdb"))),
134+
ApplyWindowsRuntimeProjections = loadReaderParameters.ApplyWindowsRuntimeProjections,
135+
AssemblyResolver = this,
136+
MetadataImporterProvider = loadReaderParameters.MetadataImporterProvider,
137+
InMemory = loadReaderParameters.InMemory,
138+
MetadataResolver = loadReaderParameters.MetadataResolver,
139+
ReadingMode = loadReaderParameters.ReadingMode,
140+
ReadSymbols = haveDebugSymbols,
141+
ReadWrite = loadReaderParameters.ReadWrite,
142+
ReflectionImporterProvider = loadReaderParameters.ReflectionImporterProvider,
143+
SymbolReaderProvider = loadReaderParameters.SymbolReaderProvider,
144+
SymbolStream = loadReaderParameters.SymbolStream,
110145
};
111146
try {
112147
return AssemblyDefinition.ReadAssembly (file, reader_parameters);

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs

+7-5
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ public TypeNameMapGenerator (IEnumerable<string> assemblies, Action<string, obje
6868

6969
Log = logMessage;
7070
var Assemblies = assemblies.ToList ();
71-
var resolver = new DirectoryAssemblyResolver (Log, loadDebugSymbols: true);
71+
var rp = new ReaderParameters (ReadingMode.Immediate);
7272

73-
foreach (var assembly in Assemblies) {
74-
resolver.Load (Path.GetFullPath (assembly));
75-
}
73+
using (var resolver = new DirectoryAssemblyResolver (Log, loadDebugSymbols: true, loadReaderParameters: rp)) {
74+
foreach (var assembly in Assemblies) {
75+
resolver.Load (Path.GetFullPath (assembly));
76+
}
7677

77-
Types = JavaTypeScanner.GetJavaTypes (Assemblies, resolver, logMessage);
78+
Types = JavaTypeScanner.GetJavaTypes (Assemblies, resolver, logMessage);
79+
}
7880
}
7981

8082
public TypeNameMapGenerator (IEnumerable<TypeDefinition> types, Action<string, object[]> logMessage)

tools/generator/CodeGenerator.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,13 @@ public static void Run (CodeGeneratorOptions options)
201201
if (options == null)
202202
throw new ArgumentNullException ("options");
203203

204+
using (var resolver = new DirectoryAssemblyResolver (Console.WriteLine, loadDebugSymbols: false)) {
205+
Run (options, resolver);
206+
}
207+
}
208+
209+
static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolver)
210+
{
204211
string assemblyQN = options.AssemblyQualifiedName;
205212
string api_level = options.ApiLevel;
206213
int product_version = options.ProductVersion;
@@ -229,7 +236,6 @@ public static void Run (CodeGeneratorOptions options)
229236

230237
// Load reference libraries
231238

232-
var resolver = new DirectoryAssemblyResolver (Console.WriteLine, loadDebugSymbols: false);
233239
foreach (var lib in options.LibraryPaths) {
234240
resolver.SearchDirectories.Add (lib);
235241
}

tools/jcw-gen/App.cs

+3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ public static int Main (string [] args)
7171
Console.Error.Write ("jcw-gen: {0}", verbosity > 0 ? e.ToString () : e.Message);
7272
return 1;
7373
}
74+
finally {
75+
resolver.Dispose ();
76+
}
7477
}
7578

7679
static void GenerateJavaCallableWrapper (TypeDefinition type, string outputPath)

0 commit comments

Comments
 (0)