Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions rayapp/tail_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package rayapp

import "bytes"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's always use a group:

import (
   "bytes"
)


// tailWriter keeps the most recent `limit` bytes written to it.
// It uses a double-buffer strategy with two bytes.Buffers: writes go
// into `active`; when active exceeds half the limit, the older buffer
// is discarded and the two are swapped. Initial memory footprint is
// near zero because bytes.Buffer grows lazily.
type tailWriter struct {
stale bytes.Buffer
active bytes.Buffer
limit int
half int
}

func newTailWriter(limit int) *tailWriter {
return &tailWriter{
limit: limit,
half: limit / 2,
}
}
Comment on lines +17 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The newTailWriter function does not validate the limit parameter. If limit is 0 or negative, the tailWriter's behavior becomes problematic. A negative limit will cause a panic in the Write method when slicing p[n-w.limit:] if n-w.limit exceeds the length of p. A limit of 0 would result in the tailWriter always returning an empty string from String() while still processing writes, which is inefficient and likely not the desired behavior. It's best to enforce a positive limit to ensure the component functions as expected and prevent runtime errors.

func newTailWriter(limit int) *tailWriter {
	if limit <= 0 {
		limit = 1 // Ensure a positive limit to prevent panics and ensure meaningful tailing.
	}
	return &tailWriter{
		limit: limit,
		half:  limit / 2,
	}
}


func (w *tailWriter) Write(p []byte) (int, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a mutex? this Write is not concurrency safe, and it is going to be taking writes from both stderr and stdout at the same time I think.

n := len(p)
if n == 0 {
return 0, nil
}

// If a single write is >= limit, keep only the last `limit` bytes.
if n >= w.limit {
w.stale.Reset()
w.active.Reset()
w.active.Write(p[n-w.limit:])
return n, nil
}

w.active.Write(p)

if w.active.Len() > w.half {
w.rotate()
}
return n, nil
}

func (w *tailWriter) rotate() {
w.stale.Reset()
w.stale, w.active = w.active, w.stale
}

// String returns the most recent `limit` bytes as a string.
func (w *tailWriter) String() string {
staleBytes := w.stale.Bytes()
activeBytes := w.active.Bytes()
total := len(staleBytes) + len(activeBytes)

if total <= w.limit {
var buf bytes.Buffer
buf.Grow(total)
buf.Write(staleBytes)
buf.Write(activeBytes)
return buf.String()
}

// Combined exceeds limit — trim from the front of stale.
skip := total - w.limit
var buf bytes.Buffer
buf.Grow(w.limit)
buf.Write(staleBytes[skip:])
buf.Write(activeBytes)
return buf.String()
}
84 changes: 84 additions & 0 deletions rayapp/tail_writer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package rayapp

import "testing"

func TestTailWriter(t *testing.T) {
t.Run("under limit", func(t *testing.T) {
tw := newTailWriter(16)
tw.Write([]byte("hello"))
if got, want := tw.String(), "hello"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
})

t.Run("multiple writes under limit", func(t *testing.T) {
tw := newTailWriter(16)
tw.Write([]byte("abc"))
tw.Write([]byte("def"))
if got, want := tw.String(), "abcdef"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
})

t.Run("exact limit", func(t *testing.T) {
tw := newTailWriter(5)
tw.Write([]byte("abcde"))
if got, want := tw.String(), "abcde"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
})

t.Run("wraps around keeps tail", func(t *testing.T) {
tw := newTailWriter(5)
tw.Write([]byte("abc"))
tw.Write([]byte("defgh"))
if got, want := tw.String(), "defgh"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
})

t.Run("multiple wraps", func(t *testing.T) {
tw := newTailWriter(4)
tw.Write([]byte("ab"))
tw.Write([]byte("cd"))
tw.Write([]byte("ef"))
if got, want := tw.String(), "cdef"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
})

t.Run("single write exceeds limit", func(t *testing.T) {
tw := newTailWriter(4)
n, err := tw.Write([]byte("abcdefgh"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 8 {
t.Errorf("Write() = %d, want 8", n)
}
if got, want := tw.String(), "efgh"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
})

t.Run("write returns full length", func(t *testing.T) {
tw := newTailWriter(4)
tw.Write([]byte("abc"))
n, err := tw.Write([]byte("defgh"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 5 {
t.Errorf("Write() = %d, want 5", n)
}
})

t.Run("empty write", func(t *testing.T) {
tw := newTailWriter(4)
tw.Write([]byte("ab"))
tw.Write([]byte(""))
if got, want := tw.String(), "ab"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
})
}