22using System . Collections . Concurrent ;
33using System . Collections . Generic ;
44using System . Diagnostics ;
5+ using System . IO ;
56using System . Linq ;
67using System . Threading ;
78using JavaScriptEngineSwitcher . Core ;
@@ -20,6 +21,10 @@ public class JavaScriptEngineFactory : IDisposable, IJavaScriptEngineFactory
2021 /// </summary>
2122 protected readonly IReactSiteConfiguration _config ;
2223 /// <summary>
24+ /// File system wrapper
25+ /// </summary>
26+ protected readonly IFileSystem _fileSystem ;
27+ /// <summary>
2328 /// Function used to create new JavaScript engine instances.
2429 /// </summary>
2530 protected readonly Func < IJsEngine > _factory ;
@@ -31,25 +36,84 @@ protected readonly ConcurrentDictionary<int, IJsEngine> _engines
3136 /// <summary>
3237 /// Pool of JavaScript engines to use
3338 /// </summary>
34- protected readonly IJsPool _pool ;
39+ protected IJsPool _pool ;
40+ /// <summary>
41+ /// Used to recycle the JavaScript engine pool when relevant JavaScript files are modified.
42+ /// </summary>
43+ protected readonly FileSystemWatcher _watcher ;
44+ /// <summary>
45+ /// Names of all the files that are loaded into the JavaScript engine. If any of these
46+ /// files are changed, the engines should be recycled
47+ /// </summary>
48+ protected readonly ISet < string > _watchedFiles ;
49+ /// <summary>
50+ /// Timer for debouncing pool recycling
51+ /// </summary>
52+ protected readonly Timer _resetPoolTimer ;
53+ /// <summary>
54+ /// Whether this class has been disposed.
55+ /// </summary>
56+ protected bool _disposed ;
57+
58+ /// <summary>
59+ /// Time period to debounce file system changed events, in milliseconds.
60+ /// </summary>
61+ protected const int DEBOUNCE_TIMEOUT = 25 ;
3562
3663 /// <summary>
3764 /// Initializes a new instance of the <see cref="JavaScriptEngineFactory"/> class.
3865 /// </summary>
39- public JavaScriptEngineFactory ( IEnumerable < Registration > availableFactories , IReactSiteConfiguration config )
66+ public JavaScriptEngineFactory (
67+ IEnumerable < Registration > availableFactories ,
68+ IReactSiteConfiguration config ,
69+ IFileSystem fileSystem
70+ )
4071 {
4172 _config = config ;
73+ _fileSystem = fileSystem ;
4274 _factory = GetFactory ( availableFactories ) ;
4375 if ( _config . ReuseJavaScriptEngines )
4476 {
45- _pool = new JsPool ( new JsPoolConfig
77+ _pool = CreatePool ( ) ;
78+ _resetPoolTimer = new Timer ( OnResetPoolTimer ) ;
79+ _watchedFiles = new HashSet < string > ( _config . Scripts . Select (
80+ fileName => _fileSystem . MapPath ( fileName ) . ToLowerInvariant ( )
81+ ) ) ;
82+ try
4683 {
47- EngineFactory = _factory ,
48- Initializer = InitialiseEngine ,
49- } ) ;
84+ // Attempt to initialise a FileSystemWatcher so we can recycle the JavaScript
85+ // engine pool when files are changed.
86+ _watcher = new FileSystemWatcher
87+ {
88+ Path = _fileSystem . MapPath ( "~" ) ,
89+ IncludeSubdirectories = true ,
90+ EnableRaisingEvents = true ,
91+ } ;
92+ _watcher . Changed += OnFileChanged ;
93+ _watcher . Created += OnFileChanged ;
94+ _watcher . Deleted += OnFileChanged ;
95+ _watcher . Renamed += OnFileChanged ;
96+ }
97+ catch ( Exception ex )
98+ {
99+ // Can't use FileSystemWatcher (eg. not running in Full Trust)
100+ Trace . WriteLine ( "Unable to initialise FileSystemWatcher: " + ex . Message ) ;
101+ }
50102 }
51103 }
52104
105+ /// <summary>
106+ /// Creates a new JavaScript engine pool.
107+ /// </summary>
108+ protected virtual IJsPool CreatePool ( )
109+ {
110+ return new JsPool ( new JsPoolConfig
111+ {
112+ EngineFactory = _factory ,
113+ Initializer = InitialiseEngine ,
114+ } ) ;
115+ }
116+
53117 /// <summary>
54118 /// Loads standard React and JSXTransformer scripts into the engine.
55119 /// </summary>
@@ -74,6 +138,7 @@ protected virtual void InitialiseEngine(IJsEngine engine)
74138 /// <returns>The JavaScript engine</returns>
75139 public virtual IJsEngine GetEngineForCurrentThread ( )
76140 {
141+ EnsureNotDisposed ( ) ;
77142 return _engines . GetOrAdd ( Thread . CurrentThread . ManagedThreadId , id =>
78143 {
79144 var engine = _factory ( ) ;
@@ -103,6 +168,7 @@ public virtual void DisposeEngineForCurrentThread()
103168 /// <returns>The JavaScript engine</returns>
104169 public virtual IJsEngine GetEngine ( )
105170 {
171+ EnsureNotDisposed ( ) ;
106172 return _pool . GetEngine ( ) ;
107173 }
108174
@@ -112,7 +178,12 @@ public virtual IJsEngine GetEngine()
112178 /// <param name="engine">Engine to return</param>
113179 public virtual void ReturnEngineToPool ( IJsEngine engine )
114180 {
115- _pool . ReturnEngineToPool ( engine ) ;
181+ // This could be called from ReactEnvironment.Dispose if that class is disposed after
182+ // this class. Let's just ignore this if it's disposed.
183+ if ( ! _disposed )
184+ {
185+ _pool . ReturnEngineToPool ( engine ) ;
186+ }
116187 }
117188
118189 /// <summary>
@@ -161,13 +232,60 @@ private static Func<IJsEngine> GetFactory(IEnumerable<Registration> availableFac
161232 /// </summary>
162233 public virtual void Dispose ( )
163234 {
235+ _disposed = true ;
164236 foreach ( var engine in _engines )
165237 {
166238 if ( engine . Value != null )
167239 {
168240 engine . Value . Dispose ( ) ;
169241 }
170242 }
243+ if ( _pool != null )
244+ {
245+ _pool . Dispose ( ) ;
246+ _pool = null ;
247+ }
248+ }
249+
250+ /// <summary>
251+ /// Handles events fired when any files are changed.
252+ /// </summary>
253+ /// <param name="sender">The sender</param>
254+ /// <param name="args">The <see cref="FileSystemEventArgs"/> instance containing the event data</param>
255+ protected virtual void OnFileChanged ( object sender , FileSystemEventArgs args )
256+ {
257+ if ( _watchedFiles . Contains ( args . FullPath . ToLowerInvariant ( ) ) )
258+ {
259+ // Use a timer so multiple changes only result in a single reset.
260+ _resetPoolTimer . Change ( DEBOUNCE_TIMEOUT , Timeout . Infinite ) ;
261+ }
262+ }
263+
264+ /// <summary>
265+ /// Called when any of the watched files have changed. Recycles the JavaScript engine pool
266+ /// so the files are all reloaded.
267+ /// </summary>
268+ /// <param name="state">Unused</param>
269+ protected virtual void OnResetPoolTimer ( object state )
270+ {
271+ // Create the new pool before disposing the old pool so that _pool is never null.
272+ var oldPool = _pool ;
273+ _pool = CreatePool ( ) ;
274+ if ( oldPool != null )
275+ {
276+ oldPool . Dispose ( ) ;
277+ }
278+ }
279+
280+ /// <summary>
281+ /// Ensures that this object has not been disposed.
282+ /// </summary>
283+ public void EnsureNotDisposed ( )
284+ {
285+ if ( _disposed )
286+ {
287+ throw new ObjectDisposedException ( GetType ( ) . Name ) ;
288+ }
171289 }
172290
173291 /// <summary>
0 commit comments