Почему данные по конвейеру между двумя процессами кажутся усеченными, когда они слишком велики? - PullRequest
1 голос
/ 14 января 2020

В последнее время мы столкнулись с проблемой в нашем проекте, когда пытались превратить канал подпроцесса в целое изображение в кодировке base64 (около 355 КБ) в его родительский процесс: но картинки казались случайно обрезанными, и мы до сих пор не получаем такое поведение и не нашли решения .

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

Вот наиболее близкий минимальный и воспроизводимый пример, который мы нам удалось создать такое поведение, у нас есть скрипт python, который пытается извлечь данные из подпроцесса узла, который генерирует данные для извлечения. Но длина данных, которую может получить родительский процесс, кажется ограниченной недетерминированным c способом.

В этом примере проверяется равенство между запрошенной длиной данных и фактической полученной длиной.

  • test.py :
#!/usr/bin/env python3

import base64
import sys
import json
import subprocess

def test(l, executable):
    process = subprocess.Popen(
        executable,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    stdout, stderr = process.communicate(input=json.dumps(l).encode())
    exit_code = process.returncode

    if exit_code != 0:
        raise RuntimeError("fail  : " + str(stderr))

    result = base64.b64decode(stdout.decode("utf-8"))
    assert len(result) == l, f"{len(result)} != {l}"
    print(f"Success: {len(result)} == {l}")

if __name__ == "__main__":
    l = int(sys.argv[1]) if len(sys.argv) > 1 else 355000
    try:
        test(l, ["./test.js"])
    except AssertionError as e:
        print("fail :", e)
  • тест. js:
#!/usr/bin/env node

const http = require("http");
const serveHandler = require("serve-handler");
const btoa = require("btoa");

const EXIT_CODE_SUCCESS = 0;
const EXIT_CODE_ERROR = 4;


async function getDataFromStdin() {
    return new Promise((resolve, reject) => {
        let receivedData = '';

        process.stdin.on("data", chunk => {
            receivedData += chunk.toString();
        });

        process.stdin.on("end", () => {
            result = resolve(JSON.parse(receivedData)); 
            return result;
        });
    })
}

async function main(){
    const len  = await getDataFromStdin();
    const base64 = btoa("0".repeat(Number(len)));
    process.stdout.write(base64);    
}

let errorCode = EXIT_CODE_SUCCESS;
main()
    .catch(err => {
        console.error(err);
        errorCode = EXIT_CODE_ERROR;
    }).finally(() => {
        process.exit(errorCode);
    });
  • output :
vagrant@sc-dev-machine:/home/vagrant $ ./test.py 1
Success: 1 == 1
vagrant@sc-dev-machine:/home/vagrant $ ./test.py 1000
Success: 1000 == 1000
vagrant@sc-dev-machine:/home/vagrant $ ./test.py 30000
Success: 30000 == 30000
vagrant@sc-dev-machine:/home/vagrant $ ./test.py 60000
fail : 49152 != 60000
vagrant@sc-dev-machine:/home/vagrant $ ./test.py 60000
Success: 60000 == 60000
vagrant@sc-dev-machine:/home/vagrant $ ./test.py 120000
fail : 49152 != 120000
vagrant@sc-dev-machine:/home/vagrant $ ./test.py 120000
fail : 98304 != 120000
vagrant@sc-dev-machine:/home/vagrant $ 

Мы также попробовали решение на основе subprocess.check_output(), но без лучшего результата.

Каким может быть объяснение этому? EOF завершил порции данных между процессами и по каналу? Разве буферизация (которая, как мы подозреваем, является причиной) не способна передавать целые данные?

Существует ли проверенный подход для передачи данных (например, файлов или изображений) в процессе без ограничений по длине?


edit: Вот также некоторые сведения об окружающей среде:

vagrant@sc-dev-machine:/home/vagrant $ uname -a
Linux sc-dev-machine 4.15.0-74-generic #84-Ubuntu SMP Thu Dec 19 08:06:28 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
vagrant@sc-dev-machine:/home/vagrant $ python3 --version
Python 3.6.8

1 Ответ

1 голос
/ 15 января 2020

Проблема в вашем JavaScript коде, и вы можете найти объяснение здесь :

Вызов process.exit() заставит процесс завершиться как можно быстрее, даже если все еще существуют незавершенные асинхронные операции, которые еще не завершены полностью, включая операции ввода-вывода для process.stdout и process.stderr.

и:

В большинстве В таких ситуациях нет необходимости явно вызывать process.exit(). Процесс Node.js завершится сам по себе, если в событии l oop не выполняется дополнительная работа. Свойство process.exitCode может быть установлено, чтобы сообщить процессу, какой код завершения использовать, когда процесс завершается корректно.

Вы звоните process.exit() до завершения вызова process.stdout.write() ( запись в каналы асинхронна в POSIX ). Это приводит к преждевременному завершению процесса JS и прерывает запись до того, как будут записаны все данные.

Если вы хотите установить код ошибки, вы должны установить process.exitCode = errorCode, как вы делаете, и позволить событию l oop завершиться изящно, не вызывая process.exit().

...