Skip to content

Commit 612205f

Browse files
committed
devtoolsproxy: count frames queued after the worker stops
Pump reports a disconnect as soon as one direction fails, while the other can still forward, so a frame can reach the queue after the final drain. Nothing reads it, and it was not counted either, so cdp_disconnect under-reported loss for exactly the commands at the end of a session.
1 parent e54dc36 commit 612205f

2 files changed

Lines changed: 47 additions & 5 deletions

File tree

server/lib/devtoolsproxy/cdpobserver.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,26 @@ func (o *cdpObserver) Observe(msg []byte, ts int64) {
104104
}
105105

106106
// Dropped reports how many forwarded frames the classifier never saw: queue
107-
// saturation and classification panics. Reported on cdp_disconnect so a reader
108-
// sees the loss rather than only the VM's log. A saturated queue rejects
109-
// whatever arrives next, which may be library traffic that would have produced
110-
// nothing, so this is an upper bound on commands lost rather than a count.
107+
// saturation, classification panics, and anything still queued once the worker
108+
// has stopped. Reported on cdp_disconnect so a reader sees the loss rather than
109+
// only the VM's log. A saturated queue rejects whatever arrives next, which may
110+
// be library traffic that would have produced nothing, so this is an upper
111+
// bound on commands lost rather than a count.
111112
func (o *cdpObserver) Dropped() int64 {
112113
if o == nil {
113114
return 0
114115
}
115-
return o.droppedQueued.Load() + o.droppedPanicked.Load()
116+
dropped := o.droppedQueued.Load() + o.droppedPanicked.Load()
117+
// Pump calls onClose as soon as one direction fails, while the other may
118+
// still be forwarding, so a frame can be queued after the final drain. Once
119+
// the worker has stopped nothing will read it, which makes it as lost as one
120+
// the queue turned away — and silently so, unless it is counted here.
121+
select {
122+
case <-o.drained:
123+
dropped += int64(len(o.frames))
124+
default:
125+
}
126+
return dropped
116127
}
117128

118129
func (o *cdpObserver) run(ctx context.Context) {

server/lib/devtoolsproxy/cdpobserver_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,34 @@ func waitFor(t *testing.T, cond func() bool) {
215215
}
216216
t.Fatal("condition not met within 5s")
217217
}
218+
219+
// Pump reports a disconnect as soon as one direction fails, while the other can
220+
// still forward, so a frame can reach the queue after the worker has drained
221+
// and stopped. Nothing will classify it, so it has to be counted rather than
222+
// quietly left behind: telemetry_dropped is what tells a reader the tail of the
223+
// session is incomplete.
224+
func TestFramesQueuedAfterTeardownAreCountedAsLoss(t *testing.T) {
225+
pub := &countingPublisher{}
226+
ctx, cancel := context.WithCancel(context.Background())
227+
o := newCdpObserver(ctx, pub.publish, controlOn, nil, silentLogger())
228+
229+
o.Observe([]byte(clickFrame), testForwardTs)
230+
cancel()
231+
o.WaitDrained(5 * time.Second)
232+
if got := pub.n.Load(); got != 1 {
233+
t.Fatalf("published %d events before teardown, want 1", got)
234+
}
235+
if got := o.Dropped(); got != 0 {
236+
t.Fatalf("dropped = %d before the late frame, want 0", got)
237+
}
238+
239+
// The straggler the other pump direction forwarded on its way out.
240+
o.Observe([]byte(clickFrame), testForwardTs)
241+
242+
if got := pub.n.Load(); got != 1 {
243+
t.Fatalf("published %d events, want 1: the worker has stopped", got)
244+
}
245+
if got := o.Dropped(); got != 1 {
246+
t.Fatalf("dropped = %d, want 1: a frame nothing will read is a loss", got)
247+
}
248+
}

0 commit comments

Comments
 (0)