Node.js 100% CPU - вызов gettimeofday
У меня есть длительный процесс node.js, который иногда переходит на 100% процессор и перестает отвечать на запросы. В последний раз, когда я это сделал, я приложил strace
к процессу, и вот что я увидел:
Process 19523 attached - interrupt to quit
gettimeofday({1394558574, 234192}, NULL) = 0
gettimeofday({1394558574, 235381}, NULL) = 0
gettimeofday({1394558574, 306460}, NULL) = 0
mmap(0x3edab823a000, 2097152, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x3edab823a000
munmap(0x3edab823a000, 811008) = 0
munmap(0x3edab8400000, 237568) = 0
mmap(0x3edab8300000, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3edab8300000
gettimeofday({1394558574, 316435}, NULL) = 0
gettimeofday({1394558574, 316576}, NULL) = 0
gettimeofday({1394558574, 316677}, NULL) = 0
gettimeofday({1394558574, 316775}, NULL) = 0
gettimeofday({1394558574, 316879}, NULL) = 0
gettimeofday({1394558574, 316978}, NULL) = 0
gettimeofday({1394558574, 317439}, NULL) = 0
gettimeofday({1394558574, 317537}, NULL) = 0
gettimeofday({1394558574, 318139}, NULL) = 0
gettimeofday({1394558574, 318234}, NULL) = 0
gettimeofday({1394558574, 318343}, NULL) = 0
gettimeofday({1394558574, 318437}, NULL) = 0
gettimeofday({1394558574, 318530}, NULL) = 0
gettimeofday({1394558574, 318624}, NULL) = 0
gettimeofday({1394558574, 319135}, NULL) = 0
gettimeofday({1394558574, 319648}, NULL) = 0
gettimeofday({1394558574, 319769}, NULL) = 0
gettimeofday({1394558574, 319975}, NULL) = 0
futex(0x7f5b380008c8, FUTEX_WAKE_PRIVATE, 1) = 1
gettimeofday({1394558574, 322266}, NULL) = 0
gettimeofday({1394558574, 322426}, NULL) = 0
gettimeofday({1394558574, 322520}, NULL) = 0
gettimeofday({1394558574, 322759}, NULL) = 0
gettimeofday({1394558574, 322853}, NULL) = 0
gettimeofday({1394558574, 322995}, NULL) = 0
futex(0x7f5b380008c8, FUTEX_WAKE_PRIVATE, 1) = 1
gettimeofday({1394558574, 417614}, NULL) = 0
gettimeofday({1394558575, 386566}, NULL) = 0
gettimeofday({1394558575, 387704}, NULL) = 0
gettimeofday({1394558575, 463410}, NULL) = 0
mmap(0x24cfd260f000, 2097152, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x24cfd260f000
munmap(0x24cfd260f000, 987136) = 0
munmap(0x24cfd2800000, 61440) = 0
mmap(0x24cfd2700000, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x24cfd2700000
Это много вызовов gettimeofday
, и не намного больше! Что может привести к тому, что node.js застрянет так?
ОБНОВЛЕНИЕ: я обновил более старую версию node.js до 10.29 (думаю), и это исчезло. Я только что обновился до 10.33, и проблема вернулась. Я сделал немного больше прогресса в отладке его на этот раз. Первый strace:
$ sudo strace -p 11812 -c
Process 11812 attached - interrupt to quit
Process 11812 detached
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.000192 0 2780 gettimeofday
0.00 0.000000 0 1390 getrusage
0.00 0.000000 0 31 futex
0.00 0.000000 0 1390 clock_gettime
------ ----------- ----------- --------- --------- ----------------
100.00 0.000192 5591 total
Из Node.js: Как подключиться к запущенному процессу и отлаживать сервер с помощью консоли? Я узнал о прикреплении отладчика node к текущему процессу, и здесь, где я получил с этим:
$ sudo kill -s SIGUSR1 11812
$ sudo node debug -p 11812
connecting... ok
break in timers.js:79
77 }
78
79 function listOnTimeout() {
80 var msecs = this.msecs;
81 var list = this;
debug> bt
#0 timers.js:79:23
Итак, похоже, что это определенно связано с таймером, но я не знаю, как двигаться дальше по стеку, чтобы узнать, где в моем коде проблема запускается.
Ответы
Ответ 1
Я предполагаю, что кто-то вручную реализовал блокировку "setTimeout". Это может произойти, если кто-то не хочет освобождать контроль над основным потоком JS, чтобы предотвратить потенциальное состояние гонки.
По понятным причинам это плохая практика для производственного кода, но я иногда использовал его для отладки, чтобы заставить асинхронные процессы выполнять в определенном порядке.
Вы можете искать что-то глупое:
var mockAsyncFunction = function (cb) {
setTimeout(function () {
cb(null, 'dummy_result')
}, 5000);
};
var myResult = null;
mockAsyncFunction(function (err, result) {
myResult = result;
});
var timeOut = 10000; // timeout in 10 sec.
var timeStart = new Date().getTime();
while (1) {
if (new Date().getTime() - timeStart > 10000) {
break;
}
}
console.log('DONE');
или что-то более гнусное с рекурсией nextTick вроде:
var timeStart = new Date().getTime();
var recurseUntilDone = function () {
if (new Date().getTime() - timeStart < 10000) {
process.nextTick(recurseUntilDone);
} else {
console.log('Done recursing');
}
};
recurseUntilDone();
Ответ 2
Используйте node-inspector, чтобы иметь возможность приостанавливать ваш код, когда процессор равен 100% - моя ставка также относится к некоторой плохо реализованной проверке цикла пока не пройдет определенное время, но их трудно найти.
Прикрепите отладчик с помощью --debug при запуске node (т.е. node index.js --debug
) и в отдельном окне запустите node-inspector
. Используйте Chrome для подключения к сеансу отладки (url выводится из команды node -indpector) и дождитесь, пока проблема не возникнет, и приостановите выполнение, и вы сможете ее найти.
Ответ 3
Мы это видели и в производстве, и в разработке. Также ищет ответ.
Мы начали исследовать время выполнения node, но проблема настолько редка, что не получает никакого приоритета. Поскольку он полностью привязан к CPU, никаких системных вызовов он не фиксирует с помощью strace.
Это не будет синхронизированное состояние проверки цикла, потому что новый цикл Date(). getTime() не делает никаких вызовов gettimeofday (в node v0.10.29; он просто делает длинную серию наноселексов в одном pid и только futex вызывает в другом. Умный, на самом деле). То же самое для Date.now().
Ответ 4
не знаю, связано ли это, но вот простой способ раздавить процесс node: используйте массив как очередь, когда в нем есть данные. Симптомы - очень медленная скорость обработки и 100% -ный процессор.
//a = [];
a = new Array();
for (i=0; i<87370; i++) a.push({});
for (i=0; i<1000000; i++) {
a.shift();
a.push({});
if (i%1000 === 0) {
process.stdout.write(".");
//global.gc();
}
}
// node v0.10.29: 10k: 0.15 sec
// 80k: 0.17 sec
// 87369: 0.17 sec
// 87370: instant 41k, then .9k per second
// 87400: Array: instant 41k, then .9k per second
// 87400: []: instant 28k, then .9k per second
// 87400: Array, pushing 1 or 'x': .9k per second from the start
// node v0.11.13: 10k: 1.94 sec
// 16683: 3.87 sec
// 16884: uniform 237.16 sec
Я побежал perf record -F 997 -g node-v0.11.13 --perf-basic-prof script.js
, но в значительной степени все время приписывается Builtin_ArrayShift (5-6 разных гексагонов, вызываемых из ~)
Обратите внимание, что поведение имеет точку переключения, ниже которой она быстро, над ней медленная.
Поведение 0.10 и 0.11 отличается; 0.11, кажется, имеет две точки переключения (3 разных скорости точек). Точки переключения не кажутся одинаковыми между группами прогонов.
Не имеет значения, вызывается ли global.gc() или нет (с помощью переключателя --expose_gc
). Не имеет значения, находится ли push/shift в непрерывном или в блокирующем цикле, как показано выше.
Редактирование: что странно, так как это зависит как от данных, так и от числа циклов. Более короткие циклы работают быстрее (даже если они разделены на 1 тыс. Партий).
Кроме того, когда это условие существует, сдвиг/нажатие на другие (пустые) списки также замедляется до того же .9k ops/sec (node v0.10.29), но любопытно, что они могут перекрываться: 10 одновременных сдвигов/добавьте петли, каждый из которых будет вставлять .9k items/sec, т.е. в сумме 9k/sec. (Итак, есть ресурсы для 10-кратной пропускной способности - внутренний дроссель?)
Изменить: не внутренний дроссель; медленная нить использует сплошной кусок 0,8 - 1,2 мс процессора, а все остальные потоки блокируются до тех пор, пока он не завершится. Поскольку параллельные потоки должны периодически выдаваться (я использовал setImmediate), они продолжают блокироваться за процессором cpu и соответственно замедляют ход.
Ответ 5
Явный delete
из более крупных хэшей или массивов, таких как очистка внутреннего контейнера данных, также может вызывать подобные симптомы. Вложенная операция node может быть ужасно медленной:
h = {}
for (i=0; i<200000; i++) h[i] = i; // 25,000,000 / sec
for (i=0; i<1000; i++) delete h[i]; // 11,000 / sec
for (i=0; i<200000; i++) delete h[i]; // 7,700 / sec
a = new Array();
for (i=0; i<200000; i++) a[i] = i; // 50,000,000 / sec
for (i=0; i<1000; i++) delete a[i]; // 10,000 / sec
for (i=0; i<200000; i++) delete a[i]; // 8,000 / sec
// and out of curiousity...
for (i=0; i<200000; i++) a[i]; // 250,000,000 / sec
for (i=1; i<200000; i++) a[i-1] = a[i]; // 180,000,000 / sec
удаление из спины к фронту в 2 раза быстрее, но это все еще очень медленно. (примечание: назначение в новый массив 50 м/с, но в []
- только 25 м/с)
Замедление - это O (n ^ 2): удвоение размера массива до 400 тыс. умножает время выполнения. Каждая операция удаления представляет собой O (n) размера хэша/массива.
Трассировка 400k (100 секунд) не отображает никаких вызовов sytem, о которых можно говорить, и не соответствует шаблону gettimeofday(), показанному выше.
...
14:08:36.327886 munmap(0x44200000, 802816) = 0
14:08:36.328145 munmap(0xf7100000, 1183744) = 0
14:08:36.328483 clock_gettime(CLOCK_REALTIME, {1418584116, 328495018}) = 0
14:08:36.328540 clock_gettime(CLOCK_REALTIME, {1418584116, 328551680}) = 0
14:08:36.328581 clock_gettime(CLOCK_REALTIME, {1418584116, 328592628}) = 0
14:08:36.328633 futex(0x8e79a7c, FUTEX_WAKE_PRIVATE, 1) = 1
14:09:03.130336 clock_gettime(CLOCK_REALTIME, {1418584143, 130406483}) = 0
14:09:03.130479 clock_gettime(CLOCK_REALTIME, {1418584143, 130503357}) = 0
14:09:03.130560 futex(0x8e79a7c, FUTEX_WAKE_PRIVATE, 1) = 1
14:09:37.090127 clock_gettime(CLOCK_REALTIME, {1418584177, 90195108}) = 0
14:09:37.090271 clock_gettime(CLOCK_REALTIME, {1418584177, 90296760}) = 0
14:09:37.090355 futex(0x8e79a7c, FUTEX_WAKE_PRIVATE, 1) = 1
14:10:17.588552 mmap2(0x3f302000, 4231168, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x3f302000
14:10:17.588694 munmap(0x3f302000, 1040384) = 0
14:10:17.588768 munmap(0x3f709000, 8192) = 0
...