1
1
// //////////////////////////////////////////////////////////////////////////////
2
- // / @brief V8 enigne configuration
2
+ // / @brief V8 engine configuration
3
3
// /
4
4
// / @file
5
5
// /
28
28
#include " ApplicationV8.h"
29
29
30
30
#include " Basics/ConditionLocker.h"
31
+ #include " Basics/ReadLocker.h"
32
+ #include " Basics/WriteLocker.h"
31
33
#include " Logger/Logger.h"
32
34
#include " V8/v8-conv.h"
33
35
#include " V8/v8-shell.h"
@@ -65,16 +67,43 @@ namespace {
65
67
public:
66
68
V8GcThread (ApplicationV8* applicationV8)
67
69
: Thread(" v8-gc" ),
68
- _applicationV8 (applicationV8) {
70
+ _applicationV8 (applicationV8),
71
+ _lock(),
72
+ _lastGcStamp(TRI_microtime()) {
69
73
}
70
74
71
75
public:
76
+
77
+ // //////////////////////////////////////////////////////////////////////////////
78
+ // / @brief collect garbage in an endless loop (main functon of GC thread)
79
+ // //////////////////////////////////////////////////////////////////////////////
80
+
72
81
void run () {
73
82
_applicationV8->collectGarbage ();
74
83
}
75
84
85
+ // //////////////////////////////////////////////////////////////////////////////
86
+ // / @brief get the timestamp of the last GC
87
+ // //////////////////////////////////////////////////////////////////////////////
88
+
89
+ double getLastGcStamp () {
90
+ READ_LOCKER (_lock);
91
+ return _lastGcStamp;
92
+ }
93
+
94
+ // //////////////////////////////////////////////////////////////////////////////
95
+ // / @brief set the global GC timestamp
96
+ // //////////////////////////////////////////////////////////////////////////////
97
+
98
+ void updateGcStamp (double value) {
99
+ WRITE_LOCKER (_lock);
100
+ _lastGcStamp = value;
101
+ }
102
+
76
103
private:
77
104
ApplicationV8* _applicationV8;
105
+ ReadWriteLock _lock;
106
+ double _lastGcStamp;
78
107
};
79
108
80
109
}
@@ -106,6 +135,7 @@ ApplicationV8::ApplicationV8 (string const& binaryPath)
106
135
_startupModules(" js/modules" ),
107
136
_actionPath(),
108
137
_gcInterval(1000 ),
138
+ _gcFrequency(10.0 ),
109
139
_startupLoader(),
110
140
_actionLoader(),
111
141
_vocbase(0 ),
@@ -230,6 +260,10 @@ ApplicationV8::V8Context* ApplicationV8::enterContext () {
230
260
// //////////////////////////////////////////////////////////////////////////////
231
261
2
A3E2
32
262
void ApplicationV8::exitContext (V8Context* context) {
263
+ V8GcThread* gc = dynamic_cast <V8GcThread*>(_gcThread);
264
+ assert (gc != 0 );
265
+ double lastGc = gc->getLastGcStamp ();
266
+
233
267
context->_context ->Exit ();
234
268
context->_isolate ->Exit ();
235
269
delete context->_locker ;
@@ -239,39 +273,131 @@ void ApplicationV8::exitContext (V8Context* context) {
239
273
{
240
274
CONDITION_LOCKER (guard, _contextCondition);
241
275
242
- if (context->_dirt < _gcInterval) {
243
- _freeContexts.push_back (context);
276
+ if (context->_lastGcStamp + _gcFrequency < lastGc) {
277
+ LOGGER_TRACE << " periodic gc interval reached" ;
278
+ _dirtyContexts.push_back (context);
244
279
}
245
- else {
280
+ else if (context->_dirt >= _gcInterval) {
281
+ LOGGER_TRACE << " maximum number of requests reached" ;
246
282
_dirtyContexts.push_back (context);
247
283
}
284
+ else {
285
+ _freeContexts.push_back (context);
286
+ }
248
287
249
288
guard.broadcast ();
250
289
}
251
290
252
291
LOGGER_TRACE << " returned dirty V8 context" ;
253
292
}
254
293
294
+ // //////////////////////////////////////////////////////////////////////////////
295
+ // / @brief determine which of the free contexts should be picked for the GC
296
+ // //////////////////////////////////////////////////////////////////////////////
297
+
298
+ ApplicationV8::V8Context* ApplicationV8::pickContextForGc () {
299
+ size_t n = _freeContexts.size ();
300
+
301
+ if (n == 0 ) {
302
+ // this is easy...
303
+ return 0 ;
304
+ }
305
+
306
+ V8GcThread* gc = dynamic_cast <V8GcThread*>(_gcThread);
307
+ V8Context* context = 0 ;
308
+
309
+ // we got more than 1 context to clean up, pick the one with the "oldest" GC stamp
310
+ size_t pickedContextNr = 0 ; // index of context with lowest GC stamp
311
+
312
+ for (size_t i = 0 ; i < n; ++i) {
313
+ // compare last GC stamp
314
+ if (_freeContexts[i]->_lastGcStamp <= _freeContexts[pickedContextNr]->_lastGcStamp ) {
315
+ pickedContextNr = i;
316
+ }
317
+ }
318
+ // we now have the context to clean up in pickedContextNr
319
+
320
+ // this is the context to clean up
321
+ context = _freeContexts[pickedContextNr];
322
+ assert (context != 0 );
323
+
324
+ // now compare its last GC timestamp with the last global GC stamp
325
+ if (context->_lastGcStamp + _gcFrequency >= gc->getLastGcStamp ()) {
326
+ // no need yet to clean up the context
327
+ return 0 ;
328
+ }
329
+
330
+ // we'll pop the context from the vector. the context might be at any position in the vector
331
+ // so we need to move the other elements around
332
+ if (n > 1 ) {
333
+ for (size_t i = pickedContextNr; i < n - 1 ; ++i) {
334
+ _freeContexts[i] = _freeContexts[i + 1 ];
335
+ }
336
+ }
337
+ _freeContexts.pop_back ();
338
+
339
+ return context;
340
+ }
341
+
255
342
// //////////////////////////////////////////////////////////////////////////////
256
343
// / @brief runs the garbage collection
257
344
// //////////////////////////////////////////////////////////////////////////////
258
345
259
346
void ApplicationV8::collectGarbage () {
347
+ V8GcThread* gc = dynamic_cast <V8GcThread*>(_gcThread);
348
+ assert (gc != 0 );
349
+
350
+ // this flag will be set to true if we timed out waiting for a GC signal
351
+ // if set to true, the next cycle will use a reduced wait time so the GC
352
+ // can be performed more early for all dirty contexts. The flag is set
353
+ // to false again once all contexts have been cleaned up and there is nothing
354
+ // more to do
355
+ bool useReducedWait = false ;
356
+
357
+ // the time we'll wait for a signal
358
+ uint64_t regularWaitTime = (uint64_t ) (_gcFrequency * 1000.0 * 1000.0 );
359
+
360
+ // the time we'll wait for a signal when the previous wait timed out
361
+ uint64_t reducedWaitTime = (uint64_t ) (_gcFrequency * 1000.0 * 100.0 );
362
+
260
363
while (_stopping == 0 ) {
261
364
V8Context* context = 0 ;
365
+ bool gotSignal = false ;
262
366
263
367
{
264
368
CONDITION_LOCKER (guard, _contextCondition);
265
369
266
370
if (_dirtyContexts.empty ()) {
267
- guard.wait ();
371
+ uint64_t waitTime = useReducedWait ? reducedWaitTime : regularWaitTime;
372
+ // we'll wait for a signal or a timeout
373
+ gotSignal = guard.wait (waitTime);
374
+
375
+ // use a reduced wait time in the next round because we seem to be idle
376
+ // the reduced wait time will allow use to perfom GC for more contexts
377
+ useReducedWait = ! gotSignal;
268
378
}
269
379
270
380
if (! _dirtyContexts.empty ()) {
271
381
context = _dirtyContexts.back ();
272
382
_dirtyContexts.pop_back ();
383
+ useReducedWait = false ;
384
+ }
385
+ else if (! gotSignal && ! _freeContexts.empty ()) {
386
+ // we timed out waiting for a signal, so we have idle time that we can
387
+ // spend on running the GC pro-actively
388
+ // We'll pick one of the free contexts and clean it up
389
+ context = pickContextForGc ();
390
+
391
+ // there is no context to clean up, probably they all have been cleaned up
392
+ // already. increase the wait time so we don't cycle to much in the GC loop
393
+ // and waste CPU unnecessary
394
+ useReducedWait = (context != 0 );
273
395
}
274
396
}
397
+
398
+ // update last gc time
399
+ double lastGc = TRI_microtime ();
400
+ gc->updateGcStamp (lastGc);
275
401
276
402
if (context != 0 ) {
277
403
LOGGER_TRACE << " collecting V8 garbage" ;
@@ -288,6 +414,7 @@ void ApplicationV8::collectGarbage () {
288
414
delete context->_locker ;
289
415
290
416
context->_dirt = 0 ;
417
+ context->_lastGcStamp = lastGc;
291
418
292
419
{
293
420
CONDITION_LOCKER (guard, _contextCondition);
@@ -326,7 +453,8 @@ void ApplicationV8::disableActions () {
326
453
327
454
void ApplicationV8::setupOptions (map<string, basics::ProgramOptionsDescription>& options) {
328
455
options[" JAVASCRIPT Options:help-admin" ]
329
- (" javascript.gc-interval" , &_gcInterval, " JavaScript garbage collection interval (each x requests)" )
456
+ (" javascript.gc-interval" , &_gcInterval, " JavaScript request-based garbage collection interval (each x requests)" )
457
+ (" javascript.gc-frequency" , &_gcFrequency, " JavaScript time-based garbage collection frequency (each x seconds)" )
330
458
;
331
459
332
460
options[" JAVASCRIPT Options:help-admin" ]
@@ -341,8 +469,14 @@ void ApplicationV8::setupOptions (map<string, basics::ProgramOptionsDescription>
341
469
// //////////////////////////////////////////////////////////////////////////////
342
470
343
471
bool ApplicationV8::prepare () {
472
+ LOGGER_DEBUG << " V8 version: " << v8::V8::GetVersion ();
344
473
LOGGER_INFO << " using JavaScript modules path '" << _startupModules << " '" ;
345
474
475
+ if (_gcFrequency < 1 ) {
476
+ // use a minimum of 1 second for GC
477
+ _gcFrequency = 1 ;
478
+ }
479
+
346
480
// set up the startup loader
347
481
if (_startupPath.empty ()) {
348
482
LOGGER_INFO << " using built-in JavaScript startup files" ;
@@ -425,13 +559,15 @@ void ApplicationV8::shutdown () {
425
559
usleep (1000 );
426
560
_gcThread->stop ();
427
561
_gcThread->join ();
428
- delete _gcThread;
429
562
430
563
for (size_t i = 0 ; i < _nrInstances; ++i) {
431
564
shutdownV8Instance (i);
432
565
}
433
566
434
567
delete[] _contexts;
568
+
569
+ // delete GC thread after all action threads have been stopped
570
+ delete _gcThread;
435
571
}
436
572
437
573
// //////////////////////////////////////////////////////////////////////////////
@@ -528,6 +664,8 @@ bool ApplicationV8::prepareV8Instance (size_t i) {
528
664
context->_isolate ->Exit ();
529
665
delete context->_locker ;
530
666
667
+ context->_lastGcStamp = TRI_microtime ();
668
+
531
669
LOGGER_TRACE << " initialised V8 context #" << i;
532
670
533
671
_freeContexts.push_back (context);
0 commit comments