golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.17k stars 17.69k forks source link

proposal: syscall: extend the interface definition of RawConn to support non-blocking operations #61628

Open lesismal opened 1 year ago

lesismal commented 1 year ago

Related: https://github.com/golang/go/issues/15735

I think it will be more useful to provide these non-blocking methods by RawConn rather than TCPConn.

These new metheds and the new definition would be like(see the OnRead/OnWrite/EnablePollWrite:

// A RawConn is a raw network connection.
type RawConn interface {
    // Control invokes f on the underlying connection's file
    // descriptor or handle.
    // The file descriptor fd is guaranteed to remain valid while
    // f executes but not after f returns.
    Control(f func(fd uintptr)) error

    // Read invokes f on the underlying connection's file
    // descriptor or handle; f is expected to try to read from the
    // file descriptor.
    // If f returns true, Read returns. Otherwise Read blocks
    // waiting for the connection to be ready for reading and
    // tries again repeatedly.
    // The file descriptor is guaranteed to remain valid while f
    // executes but not after f returns.
    Read(f func(fd uintptr) (done bool)) error

    // Write is like Read but for writing.
    Write(f func(fd uintptr) (done bool)) error

    // This is a new method.
    // OnRead registers a callback to handle the reading event.
    // If this callback is registered, users don't need to use
    // one goroutine for each conn and read it in a for-loop.
    OnRead(func(fd uintptr))

    // This is a new method.
    // OnWrite registers a callback to handle the writing event.
    OnWrite(func(fd uintptr))

    // This is a new method.
    // EnablePollWrite enables write event on the poller.
    // Users should cache the data unsent and call EnablePollWrite(true)
    // to enable the write event when Write fails, then the OnWrite 
    // callback will be called when the fd is writable;
    // Users should  call EnablePollWrite(false) to cancel the write
    // event after cached data has been sent.
    EnablePollWrite(enable bool)
}

After providing these new methods, users will be able to choose how to handle Fd's IO:

  1. Using blocking Read/Write as before
  2. Get RawConn by conn.SyscallConn(), and use RawConn.OnRead/OnWrite/EnablePollWrite to handle IO in a non-blocking way

The old issue https://github.com/golang/go/issues/15735 was only for TCPConn, and still no solution to solve the problem of saving goroutines and blocking methods, so I open this new issue.

ianlancetaylor commented 1 year ago
  1. We can't add new methods to an existing interface. That would break the compatibility guarantee.
  2. I don't understand what OnRead does. What does "handle the reading event" mean? Note that at least with our current approaches, we can't in general know whether there is any data available to read without attempting to read something.
lesismal commented 1 year ago

I don't understand what OnRead does. What does "handle the reading event" mean? Note that at least with our current approaches, we can't in general know whether there is any data available to read without attempting to read something.

OnRead registers a callback, users no longer use for { read } in a goroutine. but when the fd is readable, runtime calls the callback and users can read data in the callback.

It's like Reactor, io events passed to the users' handler.

ianlancetaylor commented 1 year ago

I would want to see an implementation before accepting a proposal like this.

lesismal commented 1 year ago

It's also like the OnReadable in https://github.com/golang/go/issues/15735#issuecomment-266574151

I have implemented similar features in my lib, an example:

package main

import (
    "fmt"

    "github.com/lesismal/nbio"
)

func main() {
    engine := nbio.NewEngine(nbio.Config{
        Network:            "tcp",
        Addrs:              []string{":8080"},
    })

    engine.OnData(func(c *nbio.Conn, data []byte) {
        c.Write(append([]byte{}, data...))
    })

    err := engine.Start()
    if err != nil {
        fmt.Printf("nbio.Start failed: %v\n", err)
        return
    }
    defer engine.Stop()

    <-make(chan int)
}

I would want to see an implementation before accepting a proposal like this.

I haven't tried to implement these Reactor things in runtime yet, but found that maybe can do it for different Socket Conns when I read go source code. I'll try it in my spare time.