Как правильно распоряжаться дескрипторами из дочерних процессов

Я пытаюсь создать платформу для автоматического развертывания приложений, которые я создаю в NodeJS. Каждое приложение может быть запущено и остановлено с панели управления, которая обрабатывает эти операции. Панель управления выполняет следующие действия при запуске приложения:

  • запрашивает дескриптор из связывателя портов, который работает как root. Этот порт-связыватель является раздвоенным дочерним элементом панели управления.
  • теперь панель управления прослушивает запросы на этот порт. Один раз запрос получен он отправляет дескриптор клиентского сокета приложению, которое прослушивает событие "сообщение" для новых запросов.
  • как только приложение сигнализируется, панель управления освобождает весь контроль над запросом и сокетом.
  • приложение делает то, что ему нужно сделать, и выпускает и закрывает запрос и сокет.

Эта настройка работает хорошо, за исключением случаев, когда вы пытаетесь сбить сервер, который больше не требуется. Хотя сервер испускает событие "close", он по-прежнему принимает соединение, которое в конечном итоге истекает, потому что больше нет привязки requestListener.

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

порт вяжущего:

var options = {
    http: require('http'),
    https: require('https')
};
process.on('message',function( data, handle ) {

    var port = data.port,
            type = data.type,
            server;

    server = options[type].createServer();
    server.once('error', function( err ) {
        process.send({success: false, port: port, error: err});
    });
    server.listen(port,function(){
        var handle = server._handle;

        process.send({success: true, port: port}, handle);

        server.close();
        handle.close();
        server._handle = null;
        handle = null;
        server.removeAllListeners();
    });
});

панель управления:

var cp = require('child_process'),
    app,
    types = {
        http: require('http'),
        https: require('https')
    },
    binder = cp.fork('./portbinder');

binder.on('message', function( data, handle ) {
    var server = types['http'].createServer(handler);

    server.once('close', function() {
        server._handle = null;
        handle = null;
    });
    server.listen( handle );
});

function handler( req, resp ) {
    app = app || cp.fork('./application'),
    socket = resp.socket,

    _handle = socket._handle,
    _req = {},
    _resp = {},
    _socket = {};

    // Copy transferable fields.
    for( i in req ) {
        if( typeof req[i] != 'function' && i != 'socket' && i != 'connection' && i != 'client' ) {
            _req[i] = req[i];
        }
    }

    for( i in resp ) {
        if( typeof resp[i] != 'function' && i != 'socket' && i != 'connection' && i != 'client' ) {
            _resp[i] = resp[i];
        }
    }

    for( i in socket ) {
        if( typeof socket[i] != 'function' &&
            i != '_handle' && i != '_httpMessage' &&
            i != '_idlePrev' && i != '_idleNext' && i != 'server' ) {
                _socket[i] = socket[i];
        }
    }

    // Send request to application.
    app.send({
        type: 'request',
        data: {
            req: _req,
            resp: _resp,
            socket: _socket
        }
    }, _handle );

    // Release the handle here without sending anything to the browser.
    socket._write = function() {};
    resp.end();
    socket.destroy();
    socket._handle = null;
    _handle = null;
};

приложения:

function reattach( target, properties ) {
    var i;

    for( i in properties ) {
        target[i] = properties[i];
    }

    return target;
};

function handler( req, resp ) {
    resp.end('hello world!');
};

process.on('message', function( param, handle ) {
    var _req = param.data.req,
        _resp = param.data.resp,
        _socket = param.data.socket,

    socket, resp, req;

    // Create a socket and reattach its properties.
    socket = new net.Socket({handle:handle});
    reattach( socket, _socket );

    // Create a request and reattach its properties.
    req = new http.IncomingMessage(socket);
    reattach( req, _req );

    // Create a response and reattach its properties.
    resp = new http.ServerResponse( req );
    resp.assignSocket(socket);
    reattach( resp, _resp );

    // Help closing down sockets after request is handled.
    resp.once('finish', function() {
        socket._write = function() {};
        socket.end();
        socket.destroy();
        socket._handle = null;
        handle = null;
    });

    handler( req, resp );
});

как вы можете видеть, я пытаюсь null и отсоединить все свойства _handle, которые у меня есть, но сервер handle не перестает прослушивать порт. Когда я делаю controlpanel неперехваченным исключением, он аварийно завершает работу, и связыватель портов также делает это. Но процесс узла приложения не завершается, и порт остается связанным, поэтому я geussing, что я не правильно выпускаю что-то там, но я не могу его найти.

вся настройка работает, она просто не закрывает сервер, когда я прошу его.

EDIT: я должен, вероятно, отметить, что я использую node.Яш версия v0.5.10

1 ответов


как оказалось, это неправильное толкование узла.документация js, которая привела к этой проблеме. Браузер, который я использовал, отправил заголовок keep-alive, удерживающий соединения открытыми в течение некоторого времени, чтобы толкать новые запросы через те же соединения.

когда я сделал сервер.close () я ожидал, что эти соединения будут закрыты, потому что документы говорят:

сервер перестает принимать новые подключения.

как оказалось, keep-alive соединения не закрыт, поэтому браузер все еще мог выполнять запросы, пока соединения были открыты, давая мне идею, что сервер все еще принимает соединения.

решением этого является повторное подключение requestListener к вашему серверу после его закрытия. Затем, когда вы получаете соединение, закройте поток сокетов без отправки чего-либо. Теперь никакие новые запросы не будут обработаны, и как только время соединения истечет, он закроется, и сервер, наконец, полностью выключится. И таким образом освобождая порт для других использований.

server.close();
// Drop any new request immediately, while server ditches old connections.
server.on('request', function( req, resp ) { req.socket.end(); });
server.once('close', function(){
    // Remove the listeners after the server has shutdown for real.
    server.removeAllListeners();
});

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

// All of this is no longer needed.
socket._write = function() {};
socket.end();
socket.destroy();
socket._handle = null;
handle = null;