I'm trying to implement a simple Connection type:
type Connection {
// Puts a batch of frames into a queue which can later be read.
// This method is called only from a single goroutine.
PutFrames(frames []*Frame)
// Returns a frame from the queue if present, with timeout.
// This method is called only from a single goroutine.
Read(timeout time.Duration) *Frame
}
I cannot change this interface.
The simplest solution that I've come up with is the following:
```
type Frame struct{}
type Connection struct {
mu sync.Mutex
frames []*Frame
}
func NewConnection() *Connection {
return &Connection{
sem: semaphore.NewWeighted(1),
}
}
func (c *Connection) getFrames() []*Frame {
c.mu.Lock()
defer c.mu.Unlock()
return c.frames
}
// Can be called only from one goroutine.
func (c *Connection) Read(timeout time.Duration) *Frame {
if timeout < 0 {
return nil
}
start := time.Now()
c.mu.Lock()
if len(c.frames) > 0 {
f := c.frames[0]
c.frames = c.frames[1:]
if len(c.frames) == 0 {
// Drain the permit if present.
_ = c.sem.TryAcquire(1)
}
c.mu.Unlock()
return f
}
c.mu.Unlock()
if !c.awaitFrames(timeout) {
return nil
}
return c.Read(timeout - time.Since(start))
}
func (c *Connection) awaitFrames(timeout time.Duration) bool {
done := make(chan struct{})
go func() {
_ = c.sem.Acquire(context.Background(), 1)
close(done)
}()
select {
case <-time.After(timeout):
return false
case <-done:
return true
}
}
// Can be called only from one goroutine.
func (c *Connection) PutFrames(frames []*Frame) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.frames) == 0 {
c.sem.Release(1)
}
c.frames = append(c.frames, frames...)
}
```
The problem with this solution is that it's very complex (let alone inefficient, but I don't care about performance in this case). Also, note that this solution only works because PutFrames() and Read() are only ever called from single goroutines. I'm afraid I would need to hack into Semaphore implementation to make it work for more concurrent callers.
If sync.Cond.WaitTimeout() method existed, the code could be made much simpler.
Questions:
- Are there any bugs in my solution?
- Are there are ways to make my solution simpler?
- Is it a good case for justifying adding Cond.WaitTimeout()?