Ошибка чтения из внешней команды: фатальная ошибка, все программы спят - тупик - PullRequest
0 голосов
/ 06 октября 2018

Я хочу написать сообщение mime / multipart на Python для стандартного вывода и прочитать это сообщение на Golang, используя пакет mime/multipart.Это всего лишь учебное упражнение.

Я пытался смоделировать этот пример .

output.py

#!/usr/bin/env python2.7
import sys
s = "--foo\r\nFoo: one\r\n\r\nA section\r\n" +"--foo\r\nFoo: two\r\n\r\nAnd another\r\n" +"--foo--\r\n"
print s 

main.go

package main

import (
    "io"
    "os/exec"
    "mime/multipart"
    "log"
    "io/ioutil"
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    pr,pw := io.Pipe()
    defer pw.Close()

    cmd := exec.Command("python","output.py")
    cmd.Stdout = pw

    mr := multipart.NewReader(pr,"foo")

    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            p, err := mr.NextPart()
            if err == io.EOF {
                fmt.Println("EOF")
                return
            }
            if err != nil {
                log.Fatal(err)
            }
            slurp, err := ioutil.ReadAll(p)
            if err != nil {
                log.Fatal(err)
            }
            fmt.Printf("Part : %q\n", slurp)
            return
        }
    }()

    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }

    cmd.Wait()
    wg.Wait()
}

Вывод go run main.go:

fatal error: all goroutines are asleep - deadlock!

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

1 Ответ

0 голосов
/ 07 октября 2018

Попробуйте что-то вроде этого (пояснение ниже):

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "mime/multipart"
    "os"
    "os/exec"
    "sync"

    "github.com/pkg/errors"
)

func readCommand(cmdStdout io.ReadCloser, wg *sync.WaitGroup, resc chan<- []byte, errc chan<- error) {
    defer wg.Done()
    defer close(errc)
    defer close(resc)

    mr := multipart.NewReader(cmdStdout, "foo")

    for {
        part, err := mr.NextPart()
        if err != nil {
            if err == io.EOF {
                fmt.Println("EOF")
            } else {
                errc <- errors.Wrap(err, "failed to get next part")
            }

            return
        }

        slurp, err := ioutil.ReadAll(part)
        if err != nil {
            errc <- errors.Wrap(err, "failed to read part")
            return
        }

        resc <- slurp
    }
}

func main() {
    cmd := exec.Command("python", "output.py")
    cmd.Stderr = os.Stderr
    pr, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    var wg sync.WaitGroup
    wg.Add(1)

    resc := make(chan []byte)
    errc := make(chan error)
    go readCommand(pr, &wg, resc, errc)

    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }

    for {
        select {
        case err, ok := <-errc:
            if !ok {
                errc = nil
                break
            }

            if err != nil {
                log.Fatal(errors.Wrap(err, "error from goroutine"))
            }

        case res, ok := <-resc:
            if !ok {
                resc = nil
                break
            }

            fmt.Printf("Part from goroutine: %q\n", res)
        }

        if errc == nil && resc == nil {
            break
        }
    }

    cmd.Wait()
    wg.Wait()
}

В произвольном порядке:

  • Вместо использования io.Pipe() в качестве Stdout команды, простоспросите у команды это StdoutPipe().cmd.Wait() гарантирует, что он для вас закрыт.
  • Установите cmd.Stderr на os.Stderr, чтобы вы могли видеть ошибки, генерируемые вашей программой Python.
    • Я заметил, что эта программа зависала каждый раз, когда программа Python писала со стандартной ошибкой.Теперь это не так:
  • Не делайте WaitGroup глобальной переменной;передать ссылку на него в программу.
  • Вместо того, чтобы log.Fatal() вводить внутри программы, создайте канал ошибок, чтобы сообщать об ошибках обратно в main().
  • Вместо того, чтобы печатать результаты внутриgoroutine, создайте канал результатов для передачи результатов обратно на main().
  • Убедитесь, что каналы закрыты для предотвращения блокировки / утечки goroutine.
  • Разделите goroutine на правильную функцию для создания кодалегче читать и следовать.
  • В этом примере мы можем создать multipart.Reader() внутри нашей программы, поскольку это единственная часть нашего кода, которая его использует.
  • Обратите внимание, что яиспользуя Wrap() из пакета errors, чтобы добавить контекст к сообщениям об ошибках.Это, конечно, не относится к вашему вопросу, но это хорошая привычка.

Часть for { select { ... } } может сбивать с толку. Эта - одна статья, которую я нашел, представляя концепцию.По сути, select позволяет нам читать с любого из этих двух каналов (resc и errc) в настоящее время для чтения, а затем устанавливать каждый на nil, когда канал закрыт.Когда оба канала nil, цикл завершается.Это позволяет нам обрабатывать «либо результат, либо ошибку» по мере их поступления.

Edit: Как сказал johandalabacka на форуме Golang , он выглядит как основнойПроблема заключалась в том, что Python в Windows добавлял к выводу дополнительный \r, и проблема в том, что ваша программа на Python должна пропустить \r в выходной строке или sys.stdout.write() вместо print() ing.Вывод также может быть очищен на стороне Golang, но, помимо невозможности правильного анализа без модификации стороны Python, этот ответ все же улучшит механику параллелизма вашей программы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...