Skip to content

Commit

Permalink
Buffer N bytes from the host and show to clients (#189)
Browse files Browse the repository at this point in the history
Clients can see empty screen when they join the hosts. This buffers the
last N bytes from the host and show to the clients when they first join.
  • Loading branch information
owenthereal authored Sep 30, 2023
1 parent 9c79782 commit a7e2710
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 49 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
require (
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
github.com/google/go-github/v48 v48.2.0
github.com/stretchr/testify v1.7.0
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4
golang.org/x/term v0.12.0
)
Expand All @@ -59,6 +60,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
Expand All @@ -73,6 +75,7 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
Expand Down
2 changes: 1 addition & 1 deletion host/internal/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Server struct {
}

func (s *Server) ServeWithContext(ctx context.Context, l net.Listener) error {
writers := uio.NewMultiWriter()
writers := uio.NewMultiWriter(5)

cmdCtx, cmdCancel := context.WithCancel(ctx)
defer cmdCancel()
Expand Down
78 changes: 59 additions & 19 deletions io/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,82 @@ import (
"sync"
)

func NewMultiWriter(writers ...io.Writer) *MultiWriter {
return &MultiWriter{writers: writers}
type buffer struct {
mu sync.Mutex

queue [][]byte
size int
}

func (c *buffer) Append(p []byte) {
c.mu.Lock()
defer c.mu.Unlock()

// remove first element if queue is full
if len(c.queue) >= c.size {
c.queue = c.queue[1:]
}

pp := make([]byte, len(p))
copy(pp, p)

c.queue = append(c.queue, pp)
}

func (c *buffer) Size() int {
c.mu.Lock()
defer c.mu.Unlock()

return len(c.queue)
}

func (c *buffer) Data() [][]byte {
c.mu.Lock()
defer c.mu.Unlock()

result := make([][]byte, len(c.queue))
return append(result, c.queue...)
}

func NewMultiWriter(bufferSize int, writers ...io.Writer) *MultiWriter {
return &MultiWriter{
writers: writers,
buffer: &buffer{size: bufferSize},
}
}

// MultiWriter is a concurrent safe writer that allows appending/removing writers.
// Newly appended writers get the last write to preserve last output.
type MultiWriter struct {
mu sync.Mutex
writeMu sync.Mutex
writers []io.Writer
cache []byte

buffer *buffer
}

func (t *MultiWriter) Append(writers ...io.Writer) error {
t.mu.Lock()
defer t.mu.Unlock()

// write last cache to new writers
if len(t.cache) > 0 {
// write last buffer to new writers
if t.buffer.Size() > 0 {
for _, w := range writers {
_, err := w.Write(t.cache)
if err != nil {
return err
for _, d := range t.buffer.Data() {
_, err := w.Write(d)
if err != nil {
return err
}
}
}
}

t.writeMu.Lock()
defer t.writeMu.Unlock()
t.writers = append(t.writers, writers...)

return nil
}

func (t *MultiWriter) Remove(writers ...io.Writer) {
t.mu.Lock()
defer t.mu.Unlock()
t.writeMu.Lock()
defer t.writeMu.Unlock()

for i := len(t.writers) - 1; i > 0; i-- {
for _, v := range writers {
Expand All @@ -51,12 +93,10 @@ func (t *MultiWriter) Remove(writers ...io.Writer) {
}

func (t *MultiWriter) Write(p []byte) (n int, err error) {
t.mu.Lock()
defer t.mu.Unlock()
t.buffer.Append(p)

// reset cache
t.cache = make([]byte, len(p))
copy(t.cache, p)
t.writeMu.Lock()
defer t.writeMu.Unlock()

for _, w := range t.writers {
n, err = w.Write(p)
Expand Down
49 changes: 20 additions & 29 deletions io/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,45 @@ import (
"io"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)

func Test_MultiWriter(t *testing.T) {
assert := assert.New(t)

w1 := bytes.NewBuffer(nil)
w := NewMultiWriter(w1)
w := NewMultiWriter(1, w1)

r := bytes.NewBufferString("hello1")
_, _ = io.Copy(w, r)

want := "hello1"
got := w1.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("want=%s got=%s:\n%s", want, got, diff)
}
assert.Equal("hello1", w1.String())

// append w2
r = bytes.NewBufferString("hello2")
w2 := bytes.NewBuffer(nil)
_ = w.Append(w2)
_, _ = io.Copy(w, r)

want = "hello1hello2"
got = w1.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("want=%s got=%s:\n%s", want, got, diff)
}
assert.Equal("hello1hello2", w1.String())
assert.Equal("hello1hello2", w2.String())

want = "hello1hello2" // new writer has the last write
got = w2.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("want=%s got=%s:\n%s", want, got, diff)
}
// append w3
r = bytes.NewBufferString("hello3")
w3 := bytes.NewBuffer(nil)
_ = w.Append(w3)
_, _ = io.Copy(w, r)

assert.Equal("hello1hello2hello3", w1.String())
assert.Equal("hello1hello2hello3", w2.String())
assert.Equal("hello2hello3", w3.String())

// remove w2
r = bytes.NewBufferString("hello3")
r = bytes.NewBufferString("hello4")
w.Remove(w2)
_, _ = io.Copy(w, r)

want = "hello1hello2hello3"
got = w1.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("want=%s got=%s:\n%s", want, got, diff)
}

want = "hello1hello2" // removed writer doesn't have the latest write
got = w2.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("want=%s got=%s:\n%s", want, got, diff)
}
assert.Equal("hello1hello2hello3hello4", w1.String())
assert.Equal("hello1hello2hello3", w2.String())
assert.Equal("hello2hello3hello4", w3.String())
}

0 comments on commit a7e2710

Please sign in to comment.