Andy Melnikov (nponeccop) wrote,
Andy Melnikov
nponeccop

Category:

EventEmitter в node.js

http://nodejs.org/api/events.html

Error events are treated as a special case in node. If there is no listener for it, then the default action is to print a stack trace and exit the program.

Есть класс EventEmitter, эвент эмиттер общего назначения. Выяснилось, что если вы свое событие случайно назовете error, то это специальное имя, которое обрабатывается иначе, чем события со всеми остальными именами.

А именно, если нет ни одного хендлера, e.emit('foo', 42) не будет делать ничего, а e.emit('error', 42) будет бросать эксепшен.

Пикантности добавляет то, что эксепшен может броситься из такого места, где стектрейс не проходит ни через один написанный вами модуль.

Смысл в том, что в течение полутора лет был heisenbug, проявлявшийся крайне редко ввиду того, что есть 100500 событий, сигнализирующих об ошибке, и error только одно из них, возникавшее редко.

Стектрейсы были весьма фантастические. Трассы указывали, что эксепшен зарождался в системном модуле http и не упоминали ни нашего кода, ни каких-либо других несистемных библиотек.

Внезапно этот баг начал проявляться каждые 5 минут, что дало нам как возможность, так и мотивацию (продакшен лежал) его искать.

После вырезания всего кода, работавшего с http, обнаружили использование модуля http в модуле ws (для вебсокетов). После чего обнаружили, что простые ошибки вроде неработающего DNS приводят к возникновению исключения, которое никак не поймать и которого быть не должно.

После чего я написал ещё один тест, в котором ошибка обрабатывалась нормально, попадала в хендлер онэррор. Начали смотреть на разницу между моим кодом (который ловил ошибочную ситуацию) и продакшеном кодом. Выяснилось, что в продакшене код onerror просто эмиттил это событие через отдельный эвент-эмиттер, и это приводило к крешу. Написали тест, при котором эмит(эррор) крешился, а эмит(хуеррор), отличавшийся только именем - нет. После чего задним числом обнаружили намёк на такое поведение в документации.

Вспомнил, что это не все пикантные подробности. Есть класс Error, обладающий уникальной особенностью: его конструктор магическим образом захватывает стектрейс в месте вызова.

Произошло следующее: было создано через new Error() и брошено исключение внутри хттп, которое было поймано, но передавалось дальше по цепочке в ws и от него в наш код. Наш код передал его аргументом в EventEmitter, который его взял да бросил. А обработчик необработанных ошибок поймал и распечатал в лог имеющийся в эксепшене совершенно левый стектрейс от http до ws вместо реального места бросания.
process.on('uncaughtException', function (e) {
	console.error(e.stack)
	process.exit()
})

setInterval(function ()
{
	
}, 1000)

var E = require('events').EventEmitter

var e = new E()

function thrower1(a)
{
	thrower2(a)
}

function thrower2(a)
{
	e.emit('error', a)
}

function stack1()
{
	try {
		stack2()
	}
	catch (a)
	{
		console.log(a.stack)
		return a
	}
	return 42
}

function stack2()
{
	throw new Error('iddqd')
}

thrower1(stack1())
Печаталось вот что: (1 копия в stderr, одна в stdout):
Error: iddqd
    at stack2 (/home/foo/aaa.js:40:8)
    at stack1 (/home/foo/aaa.js:28:3)
    at Object. (/home/foo/aaa.js:43:10)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.runMain (module.js:492:10)
    at process.startup.processNextTick.process._tickCallback (node.js:244:9)
Нет ни одного упоминания о thrower1-2 или эмиттере, а в stack1-stack2 всё корректно ловится. Интересно, что если убрать process.on - то дополнительно печатается одиногое сообщение, упоминающее об эмиттере. Впрочем, все равно без трейса.
Tags: все пидарасы а я
Subscribe

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 2 comments