2
2
using System . Collections . Concurrent ;
3
3
using System . Collections . Generic ;
4
4
using System . Diagnostics ;
5
+ using System . IO ;
5
6
using System . Linq ;
6
7
using System . Threading ;
7
8
using JavaScriptEngineSwitcher . Core ;
@@ -20,6 +21,10 @@ public class JavaScriptEngineFactory : IDisposable, IJavaScriptEngineFactory
20
21
/// </summary>
21
22
protected readonly IReactSiteConfiguration _config ;
22
23
/// <summary>
24
+ /// File system wrapper
25
+ /// </summary>
26
+ protected readonly IFileSystem _fileSystem ;
27
+ /// <summary>
23
28
/// Function used to create new JavaScript engine instances.
24
29
/// </summary>
25
30
protected readonly Func < IJsEngine > _factory ;
@@ -31,25 +36,84 @@ protected readonly ConcurrentDictionary<int, IJsEngine> _engines
31
36
/// <summary>
32
37
/// Pool of JavaScript engines to use
33
38
/// </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 ;
35
62
36
63
/// <summary>
37
64
/// Initializes a new instance of the <see cref="JavaScriptEngineFactory"/> class.
38
65
/// </summary>
39
- public JavaScriptEngineFactory ( IEnumerable < Registration > availableFactories , IReactSiteConfiguration config )
66
+ public JavaScriptEngineFactory (
67
+ IEnumerable < Registration > availableFactories ,
68
+ IReactSiteConfiguration config ,
69
+ IFileSystem fileSystem
70
+ )
40
71
{
41
72
_config = config ;
73
+ _fileSystem = fileSystem ;
42
74
_factory = GetFactory ( availableFactories ) ;
43
75
if ( _config . ReuseJavaScriptEngines )
44
76
{
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
46
83
{
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
+ }
50
102
}
51
103
}
52
104
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
+
53
117
/// <summary>
54
118
/// Loads standard React and JSXTransformer scripts into the engine.
55
119
/// </summary>
@@ -74,6 +138,7 @@ protected virtual void InitialiseEngine(IJsEngine engine)
74
138
/// <returns>The JavaScript engine</returns>
75
139
public virtual IJsEngine GetEngineForCurrentThread ( )
76
140
{
141
+ EnsureNotDisposed ( ) ;
77
142
return _engines . GetOrAdd ( Thread . CurrentThread . ManagedThreadId , id =>
78
143
{
79
144
var engine = _factory ( ) ;
@@ -103,6 +168,7 @@ public virtual void DisposeEngineForCurrentThread()
103
168
/// <returns>The JavaScript engine</returns>
104
169
public virtual IJsEngine GetEngine ( )
105
170
{
171
+ EnsureNotDisposed ( ) ;
106
172
return _pool . GetEngine ( ) ;
107
173
}
108
174
@@ -112,7 +178,12 @@ public virtual IJsEngine GetEngine()
112
178
/// <param name="engine">Engine to return</param>
113
179
public virtual void ReturnEngineToPool ( IJsEngine engine )
114
180
{
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
+ }
116
187
}
117
188
118
189
/// <summary>
@@ -161,13 +232,60 @@ private static Func<IJsEngine> GetFactory(IEnumerable<Registration> availableFac
161
232
/// </summary>
162
233
public virtual void Dispose ( )
163
234
{
235
+ _disposed = true ;
164
236
foreach ( var engine in _engines )
165
237
{
166
238
if ( engine . Value != null )
167
239
{
168
240
engine . Value . Dispose ( ) ;
169
241
}
170
242
}
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
+ }
171
289
}
172
290
173
291
/// <summary>
0 commit comments