From 82fbe341e6e2fbe633f41e6b7a70fa7a11be1974 Mon Sep 17 00:00:00 2001 From: shishir gowda Date: Fri, 5 Jun 2026 06:28:50 -0400 Subject: [PATCH 1/3] observability: add SessionTimer.Reset to re-anchor session start Lets callers re-anchor the timer to the moment a participant actually joins, so wall-clock time accrued before that (auth, room setup, ICE establishment) is not counted toward reported/billed duration. Guards Advance/Reset with a mutex since the re-anchor happens on a different goroutine than the periodic reporter tick. Min-seconds/min-minutes options are preserved across Reset. Co-Authored-By: Claude Opus 4.8 (1M context) --- observability/sessiontimer.go | 18 ++++++++++++++++++ observability/sessiontimer_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/observability/sessiontimer.go b/observability/sessiontimer.go index 8338839b5..6193bd043 100644 --- a/observability/sessiontimer.go +++ b/observability/sessiontimer.go @@ -1,12 +1,14 @@ package observability import ( + "sync" "time" "github.com/livekit/protocol/utils/options" ) type SessionTimer struct { + mu sync.Mutex lastMilli int64 lastSec int64 lastMin int64 @@ -33,7 +35,23 @@ func NewSessionTimer(startTime time.Time, opts ...SessionTimerOption) *SessionTi return options.Apply(&SessionTimer{lastMilli: ts, lastSec: ts, lastMin: ts}, opts) } +// Reset re-anchors the timer to startTime so that subsequent Advance calls +// measure elapsed time from startTime. It is intended to be called before the +// first Advance (e.g. when a participant actually joins) so that wall-clock +// time accrued before startTime is not counted toward the reported duration. +// The configured min-seconds/min-minutes options are preserved. +func (h *SessionTimer) Reset(startTime time.Time) { + h.mu.Lock() + defer h.mu.Unlock() + ts := startTime.UnixMilli() + h.lastMilli = ts + h.lastSec = ts + h.lastMin = ts +} + func (h *SessionTimer) Advance(now time.Time) (millis, secs, mins int64) { + h.mu.Lock() + defer h.mu.Unlock() ts := now.UnixMilli() if ts > h.lastMilli { millis = ts - h.lastMilli diff --git a/observability/sessiontimer_test.go b/observability/sessiontimer_test.go index 05b318d04..93ac45499 100644 --- a/observability/sessiontimer_test.go +++ b/observability/sessiontimer_test.go @@ -88,4 +88,30 @@ func TestSessionTimer(t *testing.T) { require.EqualValues(t, 45, secs) require.EqualValues(t, 3, mins) }) + + t.Run("Reset re-anchors so pre-reset time is not counted", func(t *testing.T) { + ts := time.Now() + st := NewSessionTimer(ts) + + // 90s elapse before the participant actually joins; that time should not + // be billed once we re-anchor to the join time. + joinedAt := ts.Add(90 * time.Second) + st.Reset(joinedAt) + + millis, secs, mins := st.Advance(joinedAt.Add(30 * time.Second)) + require.EqualValues(t, 30000, millis) + require.EqualValues(t, 30, secs) + require.EqualValues(t, 1, mins) + }) + + t.Run("Reset preserves min-seconds option", func(t *testing.T) { + ts := time.Now() + st := NewSessionTimer(ts, WithMinSeconds(45)) + + joinedAt := ts.Add(10 * time.Second) + st.Reset(joinedAt) + + _, secs, _ := st.Advance(joinedAt.Add(time.Second)) + require.EqualValues(t, 45, secs) + }) } From 1aa390e0d61cad6d3b2256324440a939024f39bd Mon Sep 17 00:00:00 2001 From: Paul Wells Date: Thu, 11 Jun 2026 09:51:49 -0700 Subject: [PATCH 2/3] remove mutex --- observability/sessiontimer.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/observability/sessiontimer.go b/observability/sessiontimer.go index 6193bd043..77a959da4 100644 --- a/observability/sessiontimer.go +++ b/observability/sessiontimer.go @@ -1,14 +1,12 @@ package observability import ( - "sync" "time" "github.com/livekit/protocol/utils/options" ) type SessionTimer struct { - mu sync.Mutex lastMilli int64 lastSec int64 lastMin int64 @@ -36,13 +34,8 @@ func NewSessionTimer(startTime time.Time, opts ...SessionTimerOption) *SessionTi } // Reset re-anchors the timer to startTime so that subsequent Advance calls -// measure elapsed time from startTime. It is intended to be called before the -// first Advance (e.g. when a participant actually joins) so that wall-clock -// time accrued before startTime is not counted toward the reported duration. -// The configured min-seconds/min-minutes options are preserved. +// measure elapsed time from startTime. func (h *SessionTimer) Reset(startTime time.Time) { - h.mu.Lock() - defer h.mu.Unlock() ts := startTime.UnixMilli() h.lastMilli = ts h.lastSec = ts @@ -50,8 +43,6 @@ func (h *SessionTimer) Reset(startTime time.Time) { } func (h *SessionTimer) Advance(now time.Time) (millis, secs, mins int64) { - h.mu.Lock() - defer h.mu.Unlock() ts := now.UnixMilli() if ts > h.lastMilli { millis = ts - h.lastMilli From 391c7ba03fe72f5042f5975e4bfd048eb2bff34c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:53:37 +0000 Subject: [PATCH 3/3] generated protobuf --- rpc/agent.psrpc.go | 2 +- rpc/agent_dispatch.psrpc.go | 2 +- rpc/egress.psrpc.go | 2 +- rpc/ingress.psrpc.go | 2 +- rpc/io.psrpc.go | 2 +- rpc/keepalive.psrpc.go | 2 +- rpc/participant.psrpc.go | 2 +- rpc/room.psrpc.go | 2 +- rpc/roommanager.psrpc.go | 2 +- rpc/signal.psrpc.go | 2 +- rpc/sip.psrpc.go | 2 +- rpc/whip_signal.psrpc.go | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/rpc/agent.psrpc.go b/rpc/agent.psrpc.go index 3b61aab6a..434d66530 100644 --- a/rpc/agent.psrpc.go +++ b/rpc/agent.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/agent.proto package rpc diff --git a/rpc/agent_dispatch.psrpc.go b/rpc/agent_dispatch.psrpc.go index c28eb3cd1..29d546646 100644 --- a/rpc/agent_dispatch.psrpc.go +++ b/rpc/agent_dispatch.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/agent_dispatch.proto package rpc diff --git a/rpc/egress.psrpc.go b/rpc/egress.psrpc.go index 9481f9753..2cd187b02 100644 --- a/rpc/egress.psrpc.go +++ b/rpc/egress.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/egress.proto package rpc diff --git a/rpc/ingress.psrpc.go b/rpc/ingress.psrpc.go index 8e1f48bec..25faf9c1d 100644 --- a/rpc/ingress.psrpc.go +++ b/rpc/ingress.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/ingress.proto package rpc diff --git a/rpc/io.psrpc.go b/rpc/io.psrpc.go index fb1bce3c0..dc6c6f9a6 100644 --- a/rpc/io.psrpc.go +++ b/rpc/io.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/io.proto package rpc diff --git a/rpc/keepalive.psrpc.go b/rpc/keepalive.psrpc.go index a67dc376e..4735f8d58 100644 --- a/rpc/keepalive.psrpc.go +++ b/rpc/keepalive.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/keepalive.proto package rpc diff --git a/rpc/participant.psrpc.go b/rpc/participant.psrpc.go index 12a77fa87..5a3fd5e7a 100644 --- a/rpc/participant.psrpc.go +++ b/rpc/participant.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/participant.proto package rpc diff --git a/rpc/room.psrpc.go b/rpc/room.psrpc.go index 155c33c78..0b1a8b622 100644 --- a/rpc/room.psrpc.go +++ b/rpc/room.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/room.proto package rpc diff --git a/rpc/roommanager.psrpc.go b/rpc/roommanager.psrpc.go index 31b6cb7e0..9dbc22974 100644 --- a/rpc/roommanager.psrpc.go +++ b/rpc/roommanager.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/roommanager.proto package rpc diff --git a/rpc/signal.psrpc.go b/rpc/signal.psrpc.go index a2d80b7d0..acd68bb05 100644 --- a/rpc/signal.psrpc.go +++ b/rpc/signal.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/signal.proto package rpc diff --git a/rpc/sip.psrpc.go b/rpc/sip.psrpc.go index 21a4634c3..693696962 100644 --- a/rpc/sip.psrpc.go +++ b/rpc/sip.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/sip.proto package rpc diff --git a/rpc/whip_signal.psrpc.go b/rpc/whip_signal.psrpc.go index c774f38ce..29707b064 100644 --- a/rpc/whip_signal.psrpc.go +++ b/rpc/whip_signal.psrpc.go @@ -1,4 +1,4 @@ -// Code generated by protoc-gen-psrpc v0.7.0, DO NOT EDIT. +// Code generated by protoc-gen-psrpc v0.7.2, DO NOT EDIT. // source: rpc/whip_signal.proto package rpc