Skip to content

Commit

Permalink
Merge pull request #38 from Martin-Molinero/initialization-performanc…
Browse files Browse the repository at this point in the history
…e-hang-fixes

Initialization hang fix
  • Loading branch information
jaredbroad authored Nov 13, 2019
2 parents b97e38e + 3fef624 commit ee3395e
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 173 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.0.5.26
current_version = 1.0.5.28
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<dev>\d+))?
serialize =
{major}.{minor}.{patch}.{release}{dev}
Expand Down
2 changes: 1 addition & 1 deletion conda.recipe/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package:
name: pythonnet
version: "1.0.5.26"
version: "1.0.5.28"

build:
skip: True # [not win]
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ def run(self):

setup(
name="pythonnet",
version="1.0.5.26",
version="1.0.5.28",
description=".Net and Mono integration for Python",
url='https://pythonnet.github.io/',
license='MIT',
Expand Down
2 changes: 1 addition & 1 deletion src/SharedAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@
// Version Information. Keeping it simple. May need to revisit for Nuget
// See: https://codingforsmarties.wordpress.com/2016/01/21/how-to-version-assemblies-destined-for-nuget/
// AssemblyVersion can only be numeric
[assembly: AssemblyVersion("1.0.5.26")]
[assembly: AssemblyVersion("1.0.5.28")]
2 changes: 1 addition & 1 deletion src/clrmodule/ClrModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static void initclr()
{
#if USE_PYTHON_RUNTIME_VERSION
// Has no effect until SNK works. Keep updated anyways.
Version = new Version("1.0.5.26"),
Version = new Version("1.0.5.28"),
#endif
CultureInfo = CultureInfo.InvariantCulture
};
Expand Down
165 changes: 89 additions & 76 deletions src/runtime/assemblymanager.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Python.Runtime
{
Expand All @@ -17,17 +17,15 @@ internal class AssemblyManager
{
// modified from event handlers below, potentially triggered from different .NET threads
// therefore this should be a ConcurrentDictionary
private static ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>> namespaces;
//private static Dictionary<string, Dictionary<string, string>> generics;
private static AssemblyLoadEventHandler lhandler;
private static ResolveEventHandler rhandler;
private static ConcurrentDictionary<string, ConcurrentDictionary<Assembly, byte>> namespaces;
private static ConcurrentDictionary<string, Assembly> assembliesNamesCache;
private static ConcurrentDictionary<string, Type> lookupTypeCache;
private static ConcurrentQueue<Assembly> assemblies;
private static int pendingAssemblies;

// updated only under GIL?
private static Dictionary<string, int> probed;

// modified from event handlers below, potentially triggered from different .NET threads
private static ConcurrentQueue<Assembly> assemblies;
internal static List<string> pypath;
private static List<string> pypath;

private AssemblyManager()
{
Expand All @@ -40,33 +38,41 @@ private AssemblyManager()
/// </summary>
internal static void Initialize()
{
namespaces = new ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>>();
namespaces = new ConcurrentDictionary<string, ConcurrentDictionary<Assembly, byte>>();
assembliesNamesCache = new ConcurrentDictionary<string, Assembly>();
lookupTypeCache = new ConcurrentDictionary<string, Type>();
probed = new Dictionary<string, int>(32);
//generics = new Dictionary<string, Dictionary<string, string>>();
assemblies = new ConcurrentQueue<Assembly>();
pypath = new List<string>(16);

AppDomain domain = AppDomain.CurrentDomain;

lhandler = new AssemblyLoadEventHandler(AssemblyLoadHandler);
domain.AssemblyLoad += lhandler;

rhandler = new ResolveEventHandler(ResolveHandler);
domain.AssemblyResolve += rhandler;
domain.AssemblyLoad += AssemblyLoadHandler;
domain.AssemblyResolve += ResolveHandler;

Assembly[] items = domain.GetAssemblies();
foreach (Assembly a in items)
foreach (var assembly in domain.GetAssemblies())
{
try
{
ScanAssembly(a);
assemblies.Enqueue(a);
LaunchAssemblyLoader(assembly);
}
catch (Exception ex)
{
Debug.WriteLine("Error scanning assembly {0}. {1}", a, ex);
Debug.WriteLine("Error scanning assembly {0}. {1}", assembly, ex);
}
}

var safeCount = 0;
// lets wait until all assemblies are loaded
do
{
if (safeCount++ > 200)
{
throw new TimeoutException("Timeout while waiting for assemblies to load");
}

Thread.Sleep(50);
} while (pendingAssemblies > 0);
}


Expand All @@ -76,8 +82,8 @@ internal static void Initialize()
internal static void Shutdown()
{
AppDomain domain = AppDomain.CurrentDomain;
domain.AssemblyLoad -= lhandler;
domain.AssemblyResolve -= rhandler;
domain.AssemblyLoad -= AssemblyLoadHandler;
domain.AssemblyResolve -= ResolveHandler;
}


Expand All @@ -88,13 +94,41 @@ internal static void Shutdown()
/// so that we can know about assemblies that get loaded after the
/// Python runtime is initialized.
/// </summary>
/// <remarks>Scanning assemblies here caused internal hangs when calling
/// <see cref="Assembly.GetTypes"/></remarks>
private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
{
Assembly assembly = args.LoadedAssembly;
assemblies.Enqueue(assembly);
ScanAssembly(assembly);
LaunchAssemblyLoader(assembly);
}

/// <summary>
/// Launches a new task that will load the provided assembly
/// </summary>
private static void LaunchAssemblyLoader(Assembly assembly)
{
if (assembly != null)
{
Interlocked.Increment(ref pendingAssemblies);
Task.Factory.StartNew(() =>
{
try
{
if (assembliesNamesCache.TryAdd(assembly.GetName().Name, assembly))
{
assemblies.Enqueue(assembly);
ScanAssembly(assembly);
}
}
catch
{
// pass
}

Interlocked.Decrement(ref pendingAssemblies);
});
}
}

/// <summary>
/// Event handler for assembly resolve events. This is needed because
Expand All @@ -106,12 +140,12 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
private static Assembly ResolveHandler(object ob, ResolveEventArgs args)
{
string name = args.Name.ToLower();
foreach (Assembly a in assemblies)
foreach (var assembly in assemblies)
{
string full = a.FullName.ToLower();
var full = assembly.FullName.ToLower();
if (full.StartsWith(name))
{
return a;
return assembly;
}
}
return LoadAssemblyPath(args.Name);
Expand All @@ -133,54 +167,44 @@ internal static void UpdatePath()
{
IntPtr list = Runtime.PySys_GetObject("path");
int count = Runtime.PyList_Size(list);
var sep = Path.DirectorySeparatorChar;

if (count != pypath.Count)
{
pypath.Clear();
probed.Clear();
// add first the current path
pypath.Add("");
for (var i = 0; i < count; i++)
{
IntPtr item = Runtime.PyList_GetItem(list, i);
string path = Runtime.GetManagedString(item);
if (path != null)
{
pypath.Add(path);
pypath.Add(path == string.Empty ? path : path + sep);
}
}
}
}


/// <summary>
/// Given an assembly name, try to find this assembly file using the
/// PYTHONPATH. If not found, return null to indicate implicit load
/// using standard load semantics (app base directory then GAC, etc.)
/// </summary>
public static string FindAssembly(string name)
{
char sep = Path.DirectorySeparatorChar;

foreach (string head in pypath)
foreach (var head in pypath)
{
string path;
if (head == null || head.Length == 0)
{
path = name;
}
else
var dll = $"{head}{name}.dll";
if (File.Exists(dll))
{
path = head + sep + name;
return dll;
}

string temp = path + ".dll";
if (File.Exists(temp))
{
return temp;
}

temp = path + ".exe";
if (File.Exists(temp))
var executable = $"{head}{name}.exe";
if (File.Exists(executable))
{
return temp;
return executable;
}
}
return null;
Expand All @@ -200,10 +224,7 @@ public static Assembly LoadAssembly(string name)
}
catch (Exception)
{
//if (!(e is System.IO.FileNotFoundException))
//{
// throw;
//}
// ignored
}
return assembly;
}
Expand All @@ -214,7 +235,7 @@ public static Assembly LoadAssembly(string name)
/// </summary>
public static Assembly LoadAssemblyPath(string name)
{
string path = FindAssembly(name);
var path = FindAssembly(name);
Assembly assembly = null;
if (path != null)
{
Expand All @@ -224,6 +245,7 @@ public static Assembly LoadAssemblyPath(string name)
}
catch (Exception)
{
// ignored
}
}
return assembly;
Expand Down Expand Up @@ -251,6 +273,7 @@ public static Assembly LoadAssemblyFullPath(string name)
}
catch (Exception)
{
// ignored
}
}
}
Expand All @@ -262,14 +285,8 @@ public static Assembly LoadAssemblyFullPath(string name)
/// </summary>
public static Assembly FindLoadedAssembly(string name)
{
foreach (Assembly a in assemblies)
{
if (a.GetName().Name == name)
{
return a;
}
}
return null;
Assembly result;
return assembliesNamesCache.TryGetValue(name, out result) ? result : null;
}

/// <summary>
Expand Down Expand Up @@ -306,10 +323,6 @@ public static bool LoadImplicit(string name, bool warn = true)
{
a = LoadAssemblyPath(s);
}
if (a == null)
{
a = LoadAssembly(s);
}
if (a != null && !assembliesSet.Contains(a))
{
loaded = true;
Expand Down Expand Up @@ -344,12 +357,6 @@ internal static void ScanAssembly(Assembly assembly)
// gather a list of all of the namespaces contributed to by
// the assembly.

// skip this assembly, it causes 'GetTypes' call to hang
if (assembly.FullName.StartsWith("System.Windows.Forms"))
{
return;
}

Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
Expand All @@ -361,13 +368,13 @@ internal static void ScanAssembly(Assembly assembly)
for (var n = 0; n < names.Length; n++)
{
s = n == 0 ? names[0] : s + "." + names[n];
namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, string>());
namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, byte>());
}
}

if (ns != null)
{
namespaces[ns].TryAdd(assembly, string.Empty);
namespaces[ns].TryAdd(assembly, 1);
}

if (ns != null && t.IsGenericTypeDefinition)
Expand Down Expand Up @@ -457,11 +464,17 @@ public static List<string> GetNames(string nsname)
/// </summary>
public static Type LookupType(string qname)
{
Type type;
if (lookupTypeCache.TryGetValue(qname, out type))
{
return type;
}
foreach (Assembly assembly in assemblies)
{
Type type = assembly.GetType(qname);
type = assembly.GetType(qname);
if (type != null)
{
lookupTypeCache[qname] = type;
return type;
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/runtime/classmanager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ static ClassManager()
/// </summary>
internal static ClassBase GetClass(Type type)
{
ClassBase cb = null;
cache.TryGetValue(type, out cb);
if (cb != null)
ClassBase cb;
if (cache.TryGetValue(type, out cb))
{
return cb;
}
Expand Down
Loading

0 comments on commit ee3395e

Please sign in to comment.