IronPython with restricted AppDomain

665 Views Asked by At

I'm running into a problem with long startup times when I try to run an IronPython script engine in an AppDomain with restricted permissions. Can someone provide a canonical example of how to do this in a performant way?

No AppDomain no standard library
Time taken: 36.1022ms

No AppDomain with standard library
Time taken: 37.8556ms

I saw it mentioned somewhere that creating AppDomain instances is expensive. Would it be a best practice to create a single restricted AppDomain and reuse it?

[assembly: AllowPartiallyTrustedCallers]

public class ScriptEvaluator : MarshalByRefObject
{
    public ScriptEngine ScriptEngine { get; set; }
    public String PythonStandardLibaryPath { get; set; }
    public String PythonApplicationCodePath { get; set; }

    public ScriptEvaluator()
    {
        PythonStandardLibaryPath = Path.GetFullPath(@"..\..\..\MyProject\standard_library");
        PythonApplicationCodePath = Path.GetFullPath(@"..\..\..\MyProject\python_code");
    }

    public ScriptEvaluator InstantiateInAppDomain()
    {
        /* create app domain setup */
        var appDomainSetup = new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory };
        //appDomainSetup.ApplicationBase = Path.GetDirectoryName(Environment.CurrentDirectory);
        //appDomainSetup.ApplicationBase = Environment.CurrentDirectory;
        //appDomainSetup.DisallowBindingRedirects = false;
        //appDomainSetup.DisallowCodeDownload = true;
        //appDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

        /* assign permissions */
        var permissionSet = new PermissionSet(PermissionState.None);
        permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));
        permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
        permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read, PythonStandardLibaryPath));
        permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read, PythonApplicationCodePath));

        if (Debugger.IsAttached)
        {
            permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.UnmanagedCode));
        }

        /* create a script evaluator instance in the app domain and return a reference */
        var appDomain = AppDomain.CreateDomain("Sandbox", null, appDomainSetup, permissionSet);
        var scriptEvaluator = (ScriptEvaluator)appDomain.CreateInstanceAndUnwrap("MyProject", typeof(ScriptEvaluator).FullName);
        scriptEvaluator.CreateScriptEngine();

        return scriptEvaluator;
    }

    public ScriptEngine CreateScriptEngine()
    {
        /* create the iron IronPython engine */
        var options = new Dictionary<string, object> { { "Debug", Debugger.IsAttached } };
        ScriptEngine = Python.CreateEngine(options);

        /* add library directories to the search paths */
        var searchPaths = ScriptEngine.GetSearchPaths();
        searchPaths.Add(PythonStandardLibaryPath);
        searchPaths.Add(PythonApplicationCodePath);
        ScriptEngine.SetSearchPaths(searchPaths);

        return ScriptEngine;
    }
}

Note: I had to use AllowPartiallyTrustedCallers to get around security errors, I'm not sure if this a desirable approach.

Usage:

var scriptEvaluator = new ScriptEvaluator().InstantiateInAppDomain();
var scriptScope = scriptEvaluator.ScriptEngine.CreateScope();
script = @"from mymodule import *; a = 1 + 1; b = 2 + 2; c = a + b";

foreach (var number in Enumerable.Range(1, 10))
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    scriptEvaluator.ScriptEngine.Execute(script, scriptScope);
    stopwatch.Stop(); Console.WriteLine("Time taken: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
}

Did some profiling and it looks like the first execution of ScriptEngine.Execute is more expensive than subsequent executions.

// Time taken:     8.937ms -- InstantiateInAppDomain
// Time taken:  429.9291ms -- CreateScriptEngine
// Time taken:    9.1289ms -- CreateScope
// Time taken: 2080.1795ms -- ScriptEngine.Execute first
// Time taken:    5.2469ms -- ScriptEngine.Execute second
// Time taken:    7.9255ms -- ScriptEngine.Execute third
// Time taken:    3.1206ms -- ScriptEngine.Execute fourth
// Time taken:    3.1316ms -- ScriptEngine.Execute fifth
// Time taken:    3.0643ms -- ScriptEngine.Execute sixth

Does anyone know what permission is missing to be able to import the os module in a restricted AppDomain? Adding every permission I could find didn't work.

0

There are 0 best solutions below