logoalt Hacker News

kiitos01/21/20251 replyview on HN

You're spawning a goroutine per Read call? This is pretty bonkers inefficient, to start, and a super weird approach in any case...


Replies

bheadmaster01/21/2025

Yes, but this is just proof of concept. For any given case, you can optimize your approach to your needs. E.g. single goroutine ReadCloser:

    type ioContextReadCloser struct {
        io.ReadCloser
        ctx context.Context

        ch chan *readReq
    }

    type readReq struct {
        p   []byte
        n   *int
        err *error
        m   sync.Mutex
    }

    func NewIoContextReadCloser(ctx context.Context, rc io.ReadCloser) *ioContextReadCloser {
        rcc := &ioContextReadCloser{
            ReadCloser: rc,
            ctx:        ctx,

            ch: make(chan *readReq),
        }
        go rcc.readLoop()
        return rcc
    }

    func (rcc *ioContextReadCloser) readLoop() {
        for {
            select {
            case <-rcc.ctx.Done():
                return
            case req := <-rcc.ch:
                *req.n, *req.err = rcc.ReadCloser.Read(req.p)
                if *req.err != nil {
                    req.m.Unlock()
                    return
                }
                req.m.Unlock()
            }
        }
    }

    func (rcc *ioContextReadCloser) Read(p []byte) (n int, err error) {
        req := &readReq{p: p, n: &n, err: &err}
        req.m.Lock() // use plain mutex as signalling for efficiency
        select {
        case <-rcc.ctx.Done():
            return 0, rcc.ctx.Err()
        case rcc.ch <- req:
        }
        req.m.Lock() // wait for readLoop to unlock
        return n, err
    }
Again, this is not to say this is the right way, only that it is possible and does not require any shenanigans that e.g. Python needs when dealing with when mixing sync & async, or even different async libraries.
show 2 replies