Example #1
0
        /// <summary>
        /// This method implements compiling and execution of the script.
        /// </summary>
        void ExecuteImpl()
        {
            try
            {
                //System.Diagnostics.Debug.Assert(false);
                if (options.processFile)
                {
                    CSharpParser.InitInfo initInfo = options.initContext as CSharpParser.InitInfo;
                    if (initInfo != null && initInfo.CoInitializeSecurity)
                        ComInitSecurity(initInfo.RpcImpLevel, initInfo.EoAuthnCap);

                    if (options.local)
                        Environment.CurrentDirectory = Path.GetDirectoryName(Path.GetFullPath(options.scriptFileName));

                    if (!options.noLogo)
                    {
                        Console.WriteLine(AppInfo.appLogo);
                    }

                    if (Environment.GetEnvironmentVariable("EntryScript") == null)
                        Environment.SetEnvironmentVariable("EntryScript", Path.GetFullPath(options.scriptFileName));

                    {
                        CSSUtils.VerbosePrint("> ----------------", options);
                        CSSUtils.VerbosePrint("  TragetFramework: " + options.TargetFramework, options);
                        CSSUtils.VerbosePrint("  Provider: " + options.altCompiler, options);
                        try
                        {
                            CSSUtils.VerbosePrint("  Engine: " + Assembly.GetExecutingAssembly().Location, options);
                        }
                        catch { }

                        try
                        {
                            CSSUtils.VerbosePrint(string.Format("  Console Encoding: {0} ({1}) - {2}", Console.OutputEncoding.WebName, Console.OutputEncoding.EncodingName, (Utils.IsDefaultConsoleEncoding ? "system default" : "set by engine")), options);
                        }
                        catch { } //will fail for windows app

                        CSSUtils.VerbosePrint("  CurrentDirectory: " + Environment.CurrentDirectory, options);

                        if (!Utils.IsLinux() && options.verbose)
                        {
                            CSSUtils.VerbosePrint("  NuGet manager: " + NuGet.NuGetExeView, options);
                            CSSUtils.VerbosePrint("  NuGet cache: " + NuGet.NuGetCacheView, options);
                        }
                        CSSUtils.VerbosePrint("  Executing: " + Path.GetFullPath(options.scriptFileName), options);
                        CSSUtils.VerbosePrint("  Script arguments: ", options);
                        for (int i = 0; i < scriptArgs.Length; i++)
                            CSSUtils.VerbosePrint("    " + i + " - " + scriptArgs[i], options);
                        CSSUtils.VerbosePrint("  SearchDirectories: ", options);

                        {
                            int offset = 0;
                            if (Path.GetFullPath(options.searchDirs[0]) != Environment.CurrentDirectory)
                            {
                                CSSUtils.VerbosePrint("    0 - " + Environment.CurrentDirectory, options);
                                offset++;
                            }
                            for (int i = 0; i < options.searchDirs.Length; i++)
                                CSSUtils.VerbosePrint("    " + (i + offset) + " - " + options.searchDirs[i], options);
                        }
                        CSSUtils.VerbosePrint("> ----------------", options);
                        CSSUtils.VerbosePrint("", options);
                    }

                    // The following long comment is also reflected on Wiki

                    // Execution consist of multiple stages and some of them need to be atomic and need to be synchronized system wide. 
                    // Note: synchronization (concurrency control) may only be required for execution of a given script by two or more competing
                    // processes. If one process executes script_a.cs and another one executes script_b.cs then there is no need for any synchronization 
                    // as the script files are different and their executions do not collide with each other.

                    // ---
                    // VALIDATION
                    // First, script should be validated: assessed for having valid already compiled up to date assembly. Validation is done 
                    // by checking if the compiled assembly available at all and then comparing timestamps of the assembly and the script file.
                    // After all on checks are done all script dependencies (imports and ref assemblies) are also validated. Dependency validation is
                    // also timestamp based. For the script the dependencies are identified by parsing the script and for the assembly by extracting the 
                    // dependencies metadata injected into assembly during the last compilation by the script engine.
                    // The whole validation stage is atomic and it's synchronized system wide via SystemWideLock validatingFileLock. 
                    // SystemWideLock is a decorated Mutex-like system wide synchronization object: Mutex on Windows and file-lock on Linux. 
                    // This stage is very fast as there is no heavy lifting to be done just comparing timestamps.
                    // Timeout is infinite as there is very little chance for the stage to hang.
                    // ---
                    // COMPILATION
                    // Next, if assembly is valid (the script hasn't been changed since last compilation) the it is loaded for further execution without recompilation. 
                    // Otherwise it is compiled again. Compilation stage is also atomic, so concurrent compilations (if happen) do not try to build the assembly potentially 
                    // in the same location with the same name (e.g. caching). The whole validation stage is atomic and it's also synchronized system wide via SystemWideLock 
                    // 'compilingFileLock'. 
                    // This stage is potentially heavy. Some compilers, while being relatively fast may introduce significant startup overhead (like Roslyn). 
                    // That is why caching is a preferred execution approach.
                    // Timeout is fixed as there is a chance that the third party compiler can hang.
                    // ---
                    // EXECUTION
                    // Next, the script assembly needs to be loaded/executed.This stage is extremely heavy as the execution may take infinite time depending on business logic. 
                    // When the assembly file is loaded it is locked by CLR and none can delete/recreate it. Meaning that if any one is to recompile the assembly then this
                    // cannot be done until the execution is completed and CLR releases the assembly file. System wide synchronization doesn't make much sense in this case 
                    // as open end waiting is not practical at all. Thus it's more practical to let the compiler throw an informative locking (access denied) exception. 
                    //
                    // Note: if the assembly is loaded as in-memory file copy (options.inMemoryAsm) then the assembly locking is completely eliminated. This is in fact an extremely 
                    // attractive execution model as it eliminates any problems associated with the assembly looking during the execution. The only reason why it's not activated by 
                    // default is contradicts the traditional .NET loading model when the assembly loaded as a file. 
                    //
                    // While execution stage cannot benefit from synchronization it is still using executingFileLock synchronization object. Though the objective is not to wait 
                    // when file lock is detected but rather to detect unlocking and start compiling with a little delay. Reason for this is that (on Windows at least) CLR holds 
                    // the file lock a little bit longer event after the assembly execution is finished. It is completely undocumented CLR behaver, that is hard to catch and reproduce. 
                    // It has bee confirmed by the users that this work around helps in the intense concurrent "border line" scenarios. Though there is no warranty it will be still 
                    // valid with any future releases of CLR. There ware no reports about this behaver on Linux.
                    // ---
                    // Synchronizing the stages via the lock object that is based on the assembly file name seems like the natural and best option. However the actual assembly name 
                    // (unless caching is active) is only determined during the compilation, which needs to be synchronized (chicken egg problem). Thus script file is a more practical 
                    // (and more conservative) approach for basing synchronization objects identity. Though if the need arises the assembly-based approach can be attempted.
                    // ------------------------------------------------------
                    // The concurrency model described above was an unconditional behaver until v3.16
                    // Since v3.16 concurrency model can be chosen based on the user preferences:
                    // * ConcurrencyControl.HighResolution 
                    //      The model described above.
                    //
                    // * ConcurrencyControl.Standard 
                    //      Due to the limited choices with the system wide named synchronization objects on Linux both Validation and Compilations stages are treated as a single stage,
                    //      controlled by a single synch object compilingFileLock. 
                    //      This happens to be a good default choice for Windows as well.
                    //
                    // * ConcurrencyControl.None 
                    //      All synchronization is the responsibility of the hosting environment.

                    using (SystemWideLock validatingFileLock = new SystemWideLock(options.scriptFileName, "v"))
                    using (SystemWideLock compilingFileLock = new SystemWideLock(options.scriptFileName, null))
                    using (SystemWideLock executingFileLock = new SystemWideLock(options.scriptFileName, "e"))
                    {
                        bool lockByCSScriptEngine = false;
                        bool lockedByCompiler = false;

                        // --- VALIDATE ---
                        switch (options.concurrencyControl)
                        {
                            case ConcurrencyControl.Standard: lockedByCompiler = !compilingFileLock.Wait(3000); break;
                            case ConcurrencyControl.HighResolution: lockByCSScriptEngine = !validatingFileLock.Wait(-1); break;
                        }

                        //GetAvailableAssembly also checks timestamps
                        string assemblyFileName = options.useCompiled ? GetAvailableAssembly(options.scriptFileName) : null;

                        if (options.useCompiled && options.useSmartCaching)
                        {
                            if (assemblyFileName != null)
                            {
                                if (MetaDataItems.IsOutOfDate(options.scriptFileName, assemblyFileName))
                                {
                                    assemblyFileName = null;
                                }
                            }
                        }

                        if (options.forceCompile && assemblyFileName != null)
                        {
                            switch (options.concurrencyControl)
                            {
                                case ConcurrencyControl.Standard: /*we already acquired compiler lock*/ break;
                                case ConcurrencyControl.HighResolution: lockedByCompiler = !compilingFileLock.Wait(3000); break;
                            }

                            //If file is still locked (lockedByCompiler == true) FileDelete will throw the exception
                            Utils.FileDelete(assemblyFileName, true);
                            assemblyFileName = null;
                        }

                        if (assemblyFileName != null)
                        {
                            //OK, there is a compiled script file and it is current.
                            //Validating stage is over as we are not going to recompile the script 
                            //Release the lock immediately so other instances can do their validation if waiting.
                            validatingFileLock.Release();
                        }
                        else
                        {
                            //do not release validation lock as we will need to compile the script and any pending validations 
                            //will need to wait until we finish.
                        }

                        //add searchDirs to PATH to support search path for native dlls
                        //need to do this before compilation or execution
                        string path = Environment.GetEnvironmentVariable("PATH");
                        foreach (string s in options.searchDirs)
                            path += ";" + s;
#if net1
                            SetEnvironmentVariable("PATH", path);
#else
                        Environment.SetEnvironmentVariable("PATH", path);
#endif
                        //it is possible that there are fully compiled/cached and up to date script but no host compiled yet
                        string host = ScriptLauncherBuilder.GetLauncherName(assemblyFileName);
                        bool surrogateHostMissing = (options.useSurrogateHostingProcess &&
                                                    (!File.Exists(host) || !CSSUtils.HaveSameTimestamp(host, assemblyFileName)));


                        // --- COMPILE ---
                        if (options.buildExecutable || !options.useCompiled || (options.useCompiled && assemblyFileName == null) || options.forceCompile || surrogateHostMissing)
                        {
                            // Wait for other COMPILATION to complete(if any)

                            // infinite is not good here as it may block forever but continuing while the file is still locked will
                            // throw a nice informative exception
                            switch (options.concurrencyControl)
                            {
                                case ConcurrencyControl.Standard: /*we already acquired compiler lock*/ break;
                                case ConcurrencyControl.HighResolution: lockedByCompiler = !compilingFileLock.Wait(3000); break;
                            }

                            //no need to act on lockedByCompiler/lockedByHost as Compile(...) will throw the exception

                            if (!options.inMemoryAsm && !Utils.IsLinux())
                            {
                                // wait for other EXECUTION to complete (if any)
                                bool lockedByHost = !executingFileLock.Wait(1000);

                                if (!lockedByHost)
                                {
                                    //!lockedByHost means that if there is a host executing the assemblyFileName script it has finished the execution 
                                    //but the assemblyFileName file itself may still be locked by the IO because it's host process may be just exiting
                                    Utils.WaitForFileIdle(assemblyFileName, 1000);
                                }
                            }

                            try
                            {
                                CSSUtils.VerbosePrint("Compiling script...", options);
                                CSSUtils.VerbosePrint("", options);

                                TimeSpan initializationTime = Profiler.Stopwatch.Elapsed;
                                Profiler.Stopwatch.Reset();
                                Profiler.Stopwatch.Start();

                                assemblyFileName = Compile(options.scriptFileName);

                                if (Profiler.Stopwatch.IsRunning)
                                {
                                    Profiler.Stopwatch.Stop();
                                    TimeSpan compilationTime = Profiler.Stopwatch.Elapsed;
                                    CSSUtils.VerbosePrint("Initialization time: " + initializationTime.TotalMilliseconds + " msec", options);
                                    CSSUtils.VerbosePrint("Compilation time:    " + compilationTime.TotalMilliseconds + " msec", options);
                                    CSSUtils.VerbosePrint("> ----------------", options);
                                    CSSUtils.VerbosePrint("", options);
                                }
                            }
                            catch
                            {
                                if (!CSSUtils.IsRuntimeErrorReportingSupressed)
                                {
                                    print("Error: Specified file could not be compiled.\n");
                                    if (NuGet.newPackageWasInstalled)
                                    {
                                        print("> -----\nA new NuGet package has been installed. If some of it's components are not found you may need to restart the script again.\n> -----\n");
                                    }
                                }
                                throw;
                            }
                            finally
                            {
                                //release as soon as possible
                                validatingFileLock.Release();
                                compilingFileLock.Release();
                            }
                        }
                        else
                        {
                            Profiler.Stopwatch.Stop();
                            CSSUtils.VerbosePrint("  Loading script from cache...", options);
                            CSSUtils.VerbosePrint("", options);
                            CSSUtils.VerbosePrint("  Cache file: \n       " + assemblyFileName, options);
                            CSSUtils.VerbosePrint("> ----------------", options);
                            CSSUtils.VerbosePrint("Initialization time: " + Profiler.Stopwatch.Elapsed.TotalMilliseconds + " msec", options);
                            CSSUtils.VerbosePrint("> ----------------", options);
                        }

                        // --- EXECUTE ---
                        if (!options.supressExecution)
                        {
                            try
                            {
                                if (options.useSurrogateHostingProcess)
                                {
                                    throw new SurrogateHostProcessRequiredException(assemblyFileName, scriptArgs, options.startDebugger);
                                }

                                if (options.startDebugger)
                                {
                                    SaveDebuggingMetadata(options.scriptFileName);

                                    System.Diagnostics.Debugger.Launch();
                                    if (System.Diagnostics.Debugger.IsAttached)
                                    {
                                        System.Diagnostics.Debugger.Break();
                                    }
                                }


                                if (options.useCompiled || options.cleanupShellCommand != "")
                                {
                                    AssemblyResolver.CacheProbingResults = true; //it is reasonable safe to do the aggressive probing as we are executing only a single script (standalone execution not a script hosting model)

                                    //despite the name of the class the execution (assembly loading) will be in the current domain
                                    //I am just reusing some functionality of the RemoteExecutor class.

                                    RemoteExecutor executor = new RemoteExecutor(options.searchDirs);
                                    executor.ExecuteAssembly(assemblyFileName, scriptArgs, executingFileLock);
                                }
                                else
                                {
                                    //Load and execute assembly in a different domain to make it possible to unload assembly before clean up
                                    AssemblyExecutor executor = new AssemblyExecutor(assemblyFileName, "AsmExecution");
                                    executor.Execute(scriptArgs);
                                }
                            }
                            catch (SurrogateHostProcessRequiredException)
                            {
                                throw;
                            }
                            catch
                            {
                                if (!CSSUtils.IsRuntimeErrorReportingSupressed)
                                    print("Error: Specified file could not be executed.\n");
                                throw;
                            }

                            //cleanup
                            if (File.Exists(assemblyFileName) && !options.useCompiled && options.cleanupShellCommand == "")
                            {
                                Utils.ClearFile(assemblyFileName);
                            }

                            if (options.cleanupShellCommand != "")
                            {
                                try
                                {
                                    string counterFile = Path.Combine(GetScriptTempDir(), "counter.txt");
                                    int prevRuns = 0;

                                    if (File.Exists(counterFile))
                                        using (StreamReader sr = new StreamReader(counterFile))
                                        {
                                            prevRuns = int.Parse(sr.ReadToEnd());
                                        }

                                    if (prevRuns > options.doCleanupAfterNumberOfRuns)
                                    {
                                        prevRuns = 1;
                                        string[] cmd = options.ExtractShellCommand(options.cleanupShellCommand);
                                        if (cmd.Length > 1)
                                            Process.Start(cmd[0], cmd[1]);
                                        else
                                            Process.Start(cmd[0]);
                                    }
                                    else
                                        prevRuns++;

                                    using (StreamWriter sw = new StreamWriter(counterFile))
                                        sw.Write(prevRuns);
                                }
                                catch { }
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Exception ex = e;
                if (e is System.Reflection.TargetInvocationException)
                    ex = e.InnerException;

                if (rethrow || e is SurrogateHostProcessRequiredException)
                {
                    lastException = ex;
                }
                else
                {
                    Environment.ExitCode = 1;
                    if (!CSSUtils.IsRuntimeErrorReportingSupressed)
                    {
                        if (options.reportDetailedErrorInfo)
                            print(ex.ToString());
                        else
                            print(ex.Message); //Mono friendly
                    }
                }
            }
        }
Example #2
0
        public void ExecuteAssembly(string filename, string[] args, SystemWideLock asmLock)
        {
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolveEventHandler);
            AppDomain.CurrentDomain.ResourceResolve += new ResolveEventHandler(ResolveResEventHandler); //xaml

            asmFile = filename;

            Assembly assembly;

            if (!ExecuteOptions.options.inMemoryAsm)
            {
                assembly = Assembly.LoadFrom(filename);
            }
            else
            {
                //Load(byte[]) does not lock the assembly file as LoadFrom(filename) does
                using (FileStream fs = new FileStream(filename, FileMode.Open))
                {
                    byte[] data = new byte[fs.Length];
                    fs.Read(data, 0, data.Length);
                    string dbg = Path.ChangeExtension(filename, ".pdb");

                    if (ExecuteOptions.options.DBG && File.Exists(dbg))
                        using (FileStream fsDbg = new FileStream(dbg, FileMode.Open))
                        {
                            byte[] dbgData = new byte[fsDbg.Length];
                            fsDbg.Read(dbgData, 0, dbgData.Length);
                            assembly = Assembly.Load(data, dbgData);
                        }
                    else
                        assembly = Assembly.Load(data);
                }

                asmLock.Release();
            }

            SetScriptReflection(assembly, Path.GetFullPath(filename));

            InvokeStaticMain(assembly, args);
        }