Одновременное завершение дочерних процессов и выполнение обещаний

1

tl; dr: Функция, которую я написал, создает несколько дочерних процессов, которые разрешают обещание, когда они отправляют свои данные в сообщении. Хотя функция обертывает все эти обещания в Promise.All, функция вернется внезапно, и обещание. Все не разрешает и не отклоняет, хотя все процессы завершаются без ошибок. Есть идеи, почему это происходит?

Чтобы ускорить процесс сбора данных, у меня родительский процесс взял некоторые входные данные (даты, которые нужно запросить в базах данных SQL, если быть точным) и отправьте их на некоторое количество дочерних процессов в кусках равного размера, ожидая окончания работы детей обрабатывая их данные, завершая их результаты в большом обещании.

Хотя это работает для меньших наборов данных, для более крупных, родительское обещание просто вернется в командную строку - не будет разрешать и не отклонять или даже продолжать эту функцию. После просмотра нескольких журналов кажется, что, хотя все дочерние процессы корректно обрабатывают и отправляют свои данные, родитель не получает результаты нескольких (т.е. 2 из 10) процессов. Пропущенные сообщения появляются ближе к концу обработки данных (когда несколько дочерних процессов завершают и отправляют сообщения примерно в одно и то же время)

Сокращенный код:

// main function
function createArray(i,j) // returns an array of i empty arrays, each of length j
function chunkify(a, n, balanced) // divides array a into n chunks (balancing them in size if true) returning an array of chunks

function kidcollector(snaptimes,course) {
  var done = 1;
  var numchild  = 10
  const chunked = chunkify(snaptimes,numchild,true);

  // array of numchild promises to be resolved upon arrival of data
  var collectedPromises = _.times(numchild).map(i => {
    return new Promise((resolve, reject) => {
      var child = child_process.fork('./child.js');
      // send chunk of data to each child 
      child.send({
        times:chunked[i],
        c:course
      });

      child.on('error', (err) => {
        console.log('Child error.');
        reject(err)
      });

      child.on('message', function(m) {
        if (m.err) {
          console.log('Got error from '+ m.child, m.err);
          reject(m.err);
        } else {
          console.log('recieved data from ' + m.child + '! ' + done + ' out of ' + numchild);
          done++;
          resolve(m.data);
        }

      });
    });
  })

  return Promise.all(collectedPromises)
    .then(results => {
      // compile all data into one array then return it
    })
    .catch(err => {
      console.log("One of the kids messed up:", err);
    })
};

// child.js, a separate file

const connString = // it a secret!
const client = new Client(connString);
client.connect();

client.on('error', (err) => {
 console.error('Client error:', err.stack)
})

process.on('exit', (err) => {
  if (err) console.log(process.pid + ' has recieved error:', err);
  client.end(() => console.log(process.pid + ' has disconnected on process end', err));
})

process.on('disconnect', (err) => {
  if (err) console.log(process.pid + ' has recieved error:', err);
  client.end(() => console.log(process.pid + ' has disconnected on process disconnect'));
})

process.on('message', function(m) {
  collector(m.times,m.c,process.pid) // async function which compiles data across SQL databases
  .then(async function(subdata) {
    console.log("all done");
    await process.send({
      child: process.pid,
      data: subdata
    });
    await process.disconnect();
  })
  .catch(async function(err) {
    console.log("FAILED IN CHILD", err)
    await process.send({
      child: process.pid,
      err: err
    });
    await process.disconnect();
  })
});

Таким образом, после запуска, как ожидалось, в течение некоторого времени, ближе к концу обработки данных, журнал выглядит следующим образом:

all done // child says they're done
recieved data from 5486! 5 out of 10 // parent has received their data
5486 has disconnected on process disconnect // child disconnects
5481 processing snaptime #35 at 2017-07-31T20:26:40.322Z // child is now processing a new time from their given array
all done
recieved data from 5478! 6 out of 10
5478 has disconnected on process disconnect
5483 processing snaptime #34 at 2017-07-31T20:26:51.065Z
5485 processing snaptime #35 at 2017-07-31T20:27:01.876Z
all done // child says they're done
5477 has disconnected on process disconnect // child disconnects, but parent hasn't received data
all done
recieved data from 5481! 7 out of 10 // all good here
5481 has disconnected on process disconnect
5483 processing snaptime #35 at 2017-07-31T20:27:47.834Z
all done
5485 has disconnected on process disconnect // didn't receive message here
all done
recieved data from 5483! 8 out of 10
5483 has disconnected on process disconnect
hansy@Hansys-MacBook-Air ~/Documents/GitHub // and we're at the command line...?

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

Любые идеи относительно того, что происходит и/или как решить эту проблему, тем более, что это происходит только с большими наборами данных? (Я использую узел v8.0.0 с 10 дочерними процессами)

  • 0
    Вам нужно сконцентрировать ваш вопрос. В противном случае это слишком много для того, чтобы кто-то захотел проанализировать это.
  • 0
    Я думаю, что использование дочернего процесса не очень хорошая идея для работы с обещаниями. Поскольку родительский процесс ожидает дочерний процесс, дочерний процесс также может ожидать сообщения от родительского процесса, если у него есть такой прослушиватель. Как вы знаете, таких сообщений может быть много, и трудно понять, когда обещание должно быть выполнено. В отличие от рабочих потоков, которые ничего не ждут. Вы можете передать workerData работнику и получить от него разумный ответ. Вы можете проверить пример с Promise и Worker в документации по рабочим потокам (кстати, у child_process doc таких примеров нет)
Теги:
promise
child-process
parent-child

1 ответ

0

Ваша проблема, кажется, что process.send не возвращает обещание можно было await, но вместо этого принимает (дополнительно) обратного вызова. Поэтому ваш вызов disconnect не будет ждать отправки сообщения.

Ваш родительский процесс просто закончил, когда в очереди не было больше событий, даже если обещание еще не было достигнуто. То, что вы хотите, чтобы слушать это exit событие дочернего процесса, а не только error одна. Когда вы reject от этого, вы убедитесь, что ваш Promise.all всегда будет решать независимо от того, что происходит с дочерними процессами.

рекомендую

// parent
…
new Promise((resolve, reject) => {
  const child = child_process.fork('./child.js');
  child.on('error', reject);
  child.on('exit', reject);
  child.on('message', resolve); // should happen before exit

  child.send({
    times: chunked[i],
    c: course
  });
}).then(function(m) {
  if (m.err) {
    console.log('received error from #${i} (${m.child})', m.err);
    throw m.err;
  } else {
    console.log('received data from #${i} (${m.child})');
    return m.data;
  }
}, function(err) {
  console.log('Got abort from #${i} (${m.child})');
  throw err;
});

// child
…
process.on('message', function(m) {
  collector(m.times, m.c, process.pid) // async function which compiles data across SQL databases
  .then(function(subdata) {
    console.log(process.pid+" done");
    return {
      child: process.pid,
      data: subdata
    };
  }, function(err) {
    console.log(process.pid+" FAILED:", err)
    return {
      child: process.pid,
      err: err
    };
  }).then(function(data) {
    return new Promise(function(resolve, reject) {
      process.send(data, function(err) {
        if (err) reject(err);
        else resolve();
      });
    });
  }).catch(function(err) {
    console.log(process.pid+" FAILED to send result", err)
  }).then(function() {
    process.disconnect();
  })
});

Ещё вопросы

Сообщество Overcoder
Наверх
Меню