0

So, I'm trying to read from stdin without busy waiting, ignoring EOF as clients in my case will come and go. In C I'd use a simple select() or poll() but I'm trying to learn Go, and I'm quite frustrated by the lack of select() or poll(). I can't find a good way using select and channels in Go because a Read() will return immediately on an EOF and I'm back to busy waiting. syscall.Select() seems the best way, but Go has not bothered to implement FD_SET! sigh

So, I'm trying with cgo.

package main

/*
#include <stdlib.h>
#include <sys/select.h>
void _FD_SET(int sysfd, void *set) {
    FD_SET(sysfd, (fd_set*)set);
}
*/
import "C"

import (
    "unsafe"

But when I try to build this on my Mac I get this.

# github.com/msoulier/mlogd
could not determine kind of name for C._FD_SET

clang errors for preamble:
src/github.com/msoulier/mlogd/mlogd.go:6:14: error: expected identifier or '('
void _FD_SET(int sysfd, void *set) {
             ^
src/github.com/msoulier/mlogd/mlogd.go:6:14: error: expected ')'
src/github.com/msoulier/mlogd/mlogd.go:6:13: note: to match this '('
void _FD_SET(int sysfd, void *set) {
            ^
2 errors generated.

If I merge the imports together then most of the errors go away.

package main

/*
#include <stdlib.h>
#include <sys/select.h>
void _FD_SET(int sysfd, void *set) {
    FD_SET(sysfd, (fd_set*)set);
}
*/
import (
    "C"
    "unsafe"
    "syscall"

But there's still one.

# github.com/msoulier/mlogd
could not determine kind of name for C._FD_SET

So, I'm looking for two things here.

  1. Why am I doing wrong with cgo here? From what I've read this should "just work".
  2. Is there a better way to read indefinitely from stdin, without busy waiting, and ignoring EOF?

My environment.

msoulier@merlin:~/work/go$ go version
go version go1.6.2 darwin/amd64
msoulier@merlin:~/work/go$ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/msoulier/work/go"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.6.2/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.6.2/libexec/pkg/tool/darwin_amd64"
GO15VENDOREXPERIMENT="1"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"

Thanks in advance. Must be simpler than this.

I got this far with select.

// loop forever - we expect to be killed with a SIGTERM or SIGINT
for {
    logger.Debug("going into select on stdin")
    var r_fdset syscall.FdSet
    for i := 0; i < 16; i++ {
        r_fdset.Bits[i] = 0
    }
    r_fdset.Bits[0] = 1
    selerr := syscall.Select(1, &r_fdset, nil, nil, nil)
    if selerr != nil {
        logger.Warning(selerr)
    }

But it's returning immediately even when the input runs out.

Mike

7
  • 2: you don't need to busy wait or ignore EOF to read from stdin. If you really want to use select, it's a syscall you can use directly without cgo, but you probably don't need it. Commented Jun 24, 2016 at 23:13
  • Well I'm not sure how in either case, so I'd love an example. As I said, I tried the syscall but without FD_SET I'm not sure how to use it. Commented Jun 25, 2016 at 3:50
  • I though I got select working, but now it's returning immediately whether there's input or not. Commented Jun 25, 2016 at 4:51
  • 1
    I see that I might not have understood your question. Clients cannot "come and go" and close stdin. Once a file handle is closed, it is GONE. Commented Jun 25, 2016 at 6:29
  • So once you get EOF, that is all there ever will be on that. Commented Jun 25, 2016 at 6:30

1 Answer 1

1

In Go you do blocking operations. Everywhere.

You put those blocking operations inside goroutines which are like green threads / fibers, whatever. They are scheduled by the Go runtime and might be on real threads or they might be sharing threads.

Then you use channels to talk to the other parts of your Go program.

For your problem with stdin always being readable after an EOF just stop reading it. I have my reader goroutine below just exit.

Here, have a sample I just wrote:

package main

import (
    "fmt"
    "io"
    "os"
    "time"
)

func main() {
    var err error
    ch1 := make(chan []byte)
    ch2 := make(chan int)
    var buf []byte
    var i int
    var ok bool

    go reader(ch1)
    go counter(ch2)

    for {
        select {
        case buf, ok = <-ch1:
            if ok {
                _, err = os.Stdout.Write([]byte("input: "))
                _, err = os.Stdout.Write(buf)
            }

        case i, ok = <-ch2:
            if ok {
                _, err = fmt.Println("count", i)
            }
        }
        if err != nil {
            fmt.Println("error", err)
        }
        if !ok {
            break
        }
    }
}

func counter(ch chan<- int) {
    i := 0
    for i < 5 {
        i++
        ch <- i
        time.Sleep(time.Second)
    }
    close(ch)
}

func reader(ch chan<- []byte) {
    var r int
    var err error
    for {
        buf := make([]byte, 4000)
        r, err = os.Stdin.Read(buf)
        if r > 0 {
            ch <- buf[:r]
        }
        if err != nil {
            fmt.Println("read error", err)
            if err == io.EOF {
                break
            }
            // Or really just about any error on read is fatal
            break
        }
    }
    close(ch)
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.