Skip to content
This repository
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 265 lines (217 sloc) 9.182 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
namespace SassAndCoffee.Core
{
    using System;
    using System.Collections.Concurrent;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Threading;

    using Jurassic;

    using V8Bridge.Interface;

    class JSWorkItem
    {
        readonly internal ManualResetEventSlim _gate = new ManualResetEventSlim();

        public string Input { get; internal set; }
        public string Func { get; internal set; }
        public string Result { get; internal set; }

        public JSWorkItem(string func, string input)
        {
            Func = func;
            Input = input;
        }

        public string GetValueSync()
        {
            _gate.Wait();
            return Result;
        }
    }


    /* XXX: Why this crazy code is here:
*
* After many, many painful debugging sessions, I am forced to conclude that
* the V8 JavaScript engine can only be used on one thread. Not only does that
* mean that accesses to V8 must be synchronous and only one running at a time,
* once you touch V8 from a thread, you must use *that thread* forever. i.e. it
* is not only Thread-Unsafe, but also Thread Affinitized.
*/

    public class JavascriptBasedCompiler : IDisposable
    {
        static ConcurrentQueue<JSWorkItem> _workQueue = new ConcurrentQueue<JSWorkItem>();
        static readonly Thread _dispatcherThread;
        string _compileFuncName;
        static bool _shouldQuit;

        static JavascriptBasedCompiler()
        {
            _dispatcherThread = new Thread(() => {
                var engine = JS.CreateJavascriptCompiler();

                while(!_shouldQuit) {
                    if (_workQueue == null) {
                        break;
                    }

                    JSWorkItem item;
                    if (!_workQueue.TryDequeue(out item)) {
                        Thread.Sleep(100);

                        continue;
                    }

                    try {
                        if (item.Func == null) {
                            engine.InitializeLibrary(item.Input);
                            item.Result = "";
                        } else {
                            item.Result = engine.Compile(item.Func, item.Input);
                        }
                    } catch (Exception ex) {
                        // Note: You absolutely cannot let any exceptions bubble up, as it kills the app domain.

                        item.Result = String.Format("ENGINE FAULT - please report this if it happens frequently: {0}: {1}\n{2}", ex.GetType(), ex.Message, ex.StackTrace);

                        JS.V8FailureReason = ex;
                        if (Environment.Is64BitProcess == false) {
                            engine = new JurassicCompiler();
                        }
                    }

                    item._gate.Set();
                }
            });

            _dispatcherThread.Start();
        }
        
        internal static void shutdownJSThread()
        {
            _shouldQuit = true;
            _dispatcherThread.Join(TimeSpan.FromSeconds(10));
        }

        public JavascriptBasedCompiler(string resource, string compileFuncName)
        {
            _compileFuncName = compileFuncName;
            var workItem = new JSWorkItem(null, Utility.ResourceAsString(resource));

            _workQueue.Enqueue(workItem);
            workItem.GetValueSync();
        }

        public string Compile(string coffeeScriptCode)
        {
            var ret = new JSWorkItem(_compileFuncName, coffeeScriptCode);
            _workQueue.Enqueue(ret);
            return ret.GetValueSync();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            _workQueue = null;
        }
    }

    public class JurassicCompiler : IV8ScriptCompiler
    {
        ScriptEngine _engine;
        object _gate = 42;

        public JurassicCompiler()
        {
            _engine = new ScriptEngine();
        }

        public void InitializeLibrary(string libraryCode)
        {
            lock (_gate) {
                var t = new Thread(() => {
                    _engine.Execute(libraryCode);
                }, 10 * 1048576);
    
                t.Start();
                t.Join();
            }
        }

        public string Compile(string func, string code)
        {
            return _engine.CallGlobalFunction<string>(func, code);
        }
    }

#if FALSE
    public class IronJSCompiler : IV8ScriptCompiler
    {
        CSharp.Context _engine;
        Dictionary<string, Func<string, string>> _compilerFunc = new Dictionary<string, Func<string, string>>();

        public void InitializeLibrary(string libraryCode)
        {
            _engine = new CSharp.Context();
            _engine.Execute(libraryCode);
        }

        public string Compile(string function, string input)
        {
            lock(_engine) {
                if (!_compilerFunc.ContainsKey(function)) {
                    _compilerFunc[function] = _engine.GetFunctionAs<Func<string, string>>(function);
                }

                return _compilerFunc[function](input);
            }
        }
    }
#endif

    public static class JS
    {
        static Lazy<Type> _scriptCompilerImpl;
        static object _gate = 42;

        /* XXX: Why this crazy code is here
*
* V8 is a managed C++ DLL, which means that it must be
* architecture-specific, since it contains native code. However, our host
* is *not* architecture specific, it is AnyCPU. To facilitate this, while
* still following one of the S&C's core policies of "Don't Make The User
* Think!", we actually embed V8Bridge.dll as a resource, then at runtime,
* load it based on our actual architecture. *
*
* If this can't be done (like if we're on ARM or something weird), we fall
* back to the all-managed yet incredibly slow Jurassic engine */

        public static Exception V8FailureReason;
        static JS()
        {
            _scriptCompilerImpl = new Lazy<Type>(() => {
                if (InternetExplorerJavaScriptCompiler.IsSupported) {
                    return typeof(InternetExplorerJavaScriptCompiler);
                }

                string suffix = (Environment.Is64BitProcess ? "amd64" : "x86");
                string assemblyResource = (Environment.Is64BitProcess ?
                    "SassAndCoffee.Core.lib.amd64.V8Bridge.dll" : "SassAndCoffee.Core.lib.x86.V8Bridge.dll");

                var attemptedPaths = new[] {
                    Path.Combine(Path.GetTempPath(), String.Format("V8Bridge_{0}.dll", suffix)),
                    Path.Combine(Path.GetFullPath(@".\App_Data"), String.Format("V8Bridge_{0}.dll", suffix)),
                    Path.Combine(Path.GetFullPath("."), String.Format("V8Bridge_{0}.dll", suffix)),
                };

                string succeededPath = null;
                foreach(var path in attemptedPaths) {
                    try {
                        using (var of = File.OpenWrite(path)) {
                            Assembly.GetExecutingAssembly().GetManifestResourceStream(assemblyResource).CopyTo(of);
                        }

                        succeededPath = path;
                        break;
                    } catch (IOException) {
                    } catch (UnauthorizedAccessException) {
                    }
                }

                Assembly v8Assembly;
                try {
                    v8Assembly = Assembly.LoadFile(succeededPath);
                } catch (Exception ex) {
                    V8FailureReason = ex;

                    Console.Error.WriteLine("*** WARNING: You're on ARM, Mono, Itanium (heaven help you), or another architecture\n" +
                        "which isn't x86/amd64 on NT. Loading the Jurassic compiler, which is much slower.");

                    // Jurassic completely bites it on 64-bit, we just need to abort
                    if (Environment.Is64BitProcess == true) {
                        throw;
                    }

                    return typeof (JurassicCompiler);
                }

                return v8Assembly.GetTypes().FirstOrDefault(x => typeof (IV8ScriptCompiler).IsAssignableFrom(x)) ??
                    typeof (JurassicCompiler);
            }, LazyThreadSafetyMode.ExecutionAndPublication);
        }

        public static IV8ScriptCompiler CreateJavascriptCompiler()
        {
            lock(_gate) {
                return Activator.CreateInstance(_scriptCompilerImpl.Value) as IV8ScriptCompiler;
            }
        }

        public static void Shutdown()
        {
            JavascriptBasedCompiler.shutdownJSThread();
        }
    }
}
Something went wrong with that request. Please try again.