-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
os: Stdin.SetDeadline() fails on Linux even when stdin is a pipe #24842
Comments
It happens in my system too. func main() {
fopen, err := os.Open("/proc/self/fd/0")
if err != nil {
log.Fatal(err)
}
err = fopen.SetDeadline(time.Now().Add(time.Minute))
fmt.Println(err) // prints "<nil>"
fnew := os.NewFile(fopen.Fd(), fopen.Name())
err = fnew.SetDeadline(time.Now().Add(time.Minute))
fmt.Println(err) // prints "file type does not support deadline"
}
const (
kindNewFile newFileKind = iota
kindOpenFile
kindPipe
)
// newFile is like NewFile, but if called from OpenFile or Pipe
// (as passed in the kind parameter) it tries to add the file to
// the runtime poller.
func newFile(fd uintptr, name string, kind newFileKind) *File { |
There is unfortunately no good way to support this. The In the future 1.11 release, if you put the standard input pipe into nonblocking mode before starting the Go program, then Closing this issue because I don't see any way to fix it. |
@ianlancetaylor I am surprised that SetDeadline has not changed on its own file from blocking to non-blocking. Was was the reason for that? As the new API it could be a reasonable opt-in. In any case, why not provide an explicit API for 1.11 to make a descriptor non-blocking or have a version of SetDeadline that makes so on supported file descriptors? Without that it seems it will be still impossible to write a command line go application like a trivial cat-like program without using system-specific hacks like opening /proc/self/fd/0. Upon receiving an exit signal such application should close stdin and write all data that it read to stdout and exits. |
Changing an existing file descriptor to non-blocking mode is a significant change that can affect other programs using the same descriptor (due to pipes passed through exec). It is not a change that can be made as a side effect of some other call. We could consider an explicit API to change a descriptor to non-blocking and add it to the poller. But it seems like a very rare use case. I don't understand why you say that you have can't write a cat-like program. Clearly you can. If you are worried about signals, catch them with the os/signal package. If you want a deadline, do your reads in a goroutine and let your main program use a timer. Using |
@ianlancetaylor In my case the code is a log transformer that adds a prefix/cleanup to each line it receives on stdin connected to a pipe from another program and writes that to stdout. I want to ensure that I can restart the transformer without loosing any log messages when the transformer receives a termination signal. Without working Now, if 1.11 makes SetDeadline to work with nonblocking stdin, then at least I can do much more portable hack than opening /dev/stdin (which may not work even with mounted /proc if stdin comes from a pipe from a process with different uid) . The code can use a syscall to check if stdin is blocking, make it non-blocking and then re-exec itself. But it would be nice to be able to avoid that... |
I see how |
@ianlancetaylor In my Linux system (go1.10.1) EDIT: maybe you meant that it will work if the fd is set to non-blocking mode, which I haven't tried. |
I agree that it won't unblock a Anyhow for your specific use case it seems to me that you can do this: package main
import (
"fmt"
"os"
"syscall"
"time"
)
func main() {
if err := syscall.SetNonblock(0, true); err != nil {
panic(err)
}
f := os.NewFile(0, "stdin")
go func() {
time.Sleep(time.Millisecond)
fmt.Println("setting deadline")
if err := f.SetDeadline(time.Now().Add(-time.Millisecond)); err != nil {
panic(err)
}
}()
var buf [1]byte
_, err := f.Read(buf[:])
if err == nil {
panic("Read succeeded")
}
fmt.Println("read failed as expected:", err)
if err := syscall.SetNonblock(0, false); err != nil {
panic(err)
}
} |
My tests seem to suggest that it will complete if some data arrives:
mygoprogram: func main() {
go func() {
time.Sleep(time.Second)
syscall.Close(0)
}()
_, err := io.Copy(os.Stdout, os.Stdin)
fmt.Println("copy err:", err)
} Thanks for your useful code sample;
If I run it like this:
The spawned goroutine doesn't seem to have a chance to start in either cases (even if I print something at the top I don't see it). You probably intended for it to be run with go1.11 that will have this feature. |
You're right, I was assuming 1.11. Any change in this area would only be in 1.11 anyhow, it would not be backported to 1.10. |
I was hopping to have a solution in 1.11 without |
Changing standard input to be nonblocking is both unusual and risky, in that if it is left in nonblocking mode it can easily break the shell after the program exits. I don't think we need support for this in the os package. |
What version of Go are you using (
go version
)?go version go1.10 linux/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (
go env
)?GOARCH="amd64"
GOBIN=""
GOCACHE="/vol/var/root/.cache/go-build"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/vol/var/root/go"
GORACE=""
GOROOT="/vol/var/build/go-1.10"
GOTMPDIR=""
GOTOOLDIR="/vol/var/build/go-1.10/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build675138706=/tmp/go-build -gno-record-gcc-switches"
What did you do?
On Linux os.Stdin.SetDeadline() fails with
file type does not support deadline
even when the standard input is a pipe. The following code demonstrates this:The program creates a pipe and then run itself the second time with the stdin of the second invocation connected to the pipe. Then the second copy calls
os.Stdin.SetDeadline()
. That fails with the above mentioned error that the program prints.Then, as a workaround, the code opens the stdin again via Linux-specific
/proc/self/fd/0
and calls SetDeadline on that. This time the SetDedaline reports a success. Note that this is not a universal workaround as/proc
may not be available when, for example, the code runs in a restricted Linux container.On play.golang.org the first SetDedaline call works as expected while the attempt to open /proc/self/fd/0 fails.
What did you expect to see?
The program should print:
to indicate that both SetDedaline calls returned nil as error.
What did you see instead?
The text was updated successfully, but these errors were encountered: