Home  >  Article  >  Backend Development  >  os.OpenFile with O_RDONLY hangs on named pipe without writer

os.OpenFile with O_RDONLY hangs on named pipe without writer

王林
王林forward
2024-02-11 08:33:081208browse

带有 O_RDONLY 的 os.OpenFile 挂在没有编写器的命名管道上

php editor Banana introduces you to a special operation method, which is to use the os.OpenFile function with O_RDONLY to mount on a named pipe without a writer. This operation method can realize the reading operation of the named pipe, allowing you to easily obtain the data information in the named pipe without using a writer. This technique is simple to understand and easy to operate, making it an excellent choice for developers when dealing with named pipes. Next, we will introduce you in detail how to use this method to implement the reading operation of the named pipe.

Question content

I'm writing a daemon that should receive notifications from ad-hoc cli commands and choose to do so via unix named pipes. I wrote a short package that on one hand generates a separate goroutine to read from nodes and send received notifications to channels (playground with unit tests):

type Writer struct {
    f *os.File
}

func NewWriter(ipc string) (*Writer, error) {
    f, err := os.OpenFile(ipc, os.O_WRONLY, 0600)

    if err != nil {
        return nil, fmt.Errorf("writer: open file: %w", err)
    }

    return &Writer{f: f}, nil
}

func (w *Writer) WriteString(str string) (int, error) {
    return w.f.WriteString(fmt.Sprint(str, "\n"))

}

func (w *Writer) Close() error {
    return w.f.Close()
}

type Reader struct {
    f    *os.File
    rmFn func() error
    quit chan struct{}
    done *sync.WaitGroup
}

func NewReader(ipc string) (*Reader, error) {
    err := syscall.Mkfifo(ipc, 0640)
    if err != nil {
        return nil, fmt.Errorf("reader: create fifo: %w", err)
    }

    f, err := os.OpenFile(ipc, os.O_RDONLY, 0640)
    if err != nil {
        return nil, fmt.Errorf("reader: open fifo: %w", err)
    }
    return &Reader{
        f:    f,
        quit: make(chan struct{}),
        done: &sync.WaitGroup{},
        rmFn: func() error {
            return os.Remove(ipc)
        },
    }, nil
}

func (r *Reader) PollRead() <-chan string {
    reader := bufio.NewReader(r.f)
    out := make(chan string)
    r.done.Add(1)
    go func() {
        defer r.done.Done()
        for {
            line, err := reader.ReadBytes('\n')
            if err != nil {
                fmt.Printf("error reading from named pipe: %v\n", err)
                return
            }

            nline := string(line)
            nline = strings.TrimRight(nline, "\n")
            select {
            case out <- nline:
            case <-r.quit:
                close(out)
                return
            }
        }
    }()

    return out
}

func (r *Reader) Close() error {
    close(r.quit)
    r.done.Wait()
    err := r.f.Close()
    if err != nil {
        return fmt.Errorf("error closing named pipe: %v", err)
    }

    err = r.rmFn()
    if err != nil {
        return fmt.Errorf("error removing named pipe: %v", err)
    }
    return nil
}

This does seem to work, but it suffers from a peculiar behavior where no readers can open the file before any writers can open the file, which seems to be Based on other things I've read on the topic to reverse the behavior; the usual complaint is that the writer hangs because there aren't any readers, however, here the reader cannot be instantiated in the first place.

Solution

This is posix system interface中记录的默认行为>:

o_nonblock When opening a fifo with o_rdonly or o_wronly set: if o_nonblock is set, read-only open() will return without Delay. If there is no process, write-only open() will return an error The file is currently open for reading.

If o_nonblock is cleared, read-only open() will block Called until the thread opens the file for writing. an open() Write-only should block the calling thread until the thread is opened File for reading.

When opening a supported block special or character special file Non-blocking open:

If o_nonblock is set, the open() function will return without Prevent the device from being ready or available. subsequent behavior Device properties are device-specific.

If o_nonblock is cleared, the open() function will block the call l> The thread does not return until the device is ready or available.

Otherwise, the o_nonblock flag does not cause an error, but it is It is not specified whether the file status flag contains o_nonblock logo.

So the solution is to add the syscall.o_nonblock flag to the openfile call:

f, err := os.OpenFile(ipc, os.O_RDONLY|syscall.O_NONBLOCK, 0640)

Edit: As discussed in the comments, this solution is not portable to a darwin environment. A more portable solution is to open the file on the reader side using o_rdwr.

The above is the detailed content of os.OpenFile with O_RDONLY hangs on named pipe without writer. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete