This commit is contained in:
2026-06-20 19:16:44 +08:00
parent db3d030169
commit dbb87823e8
26 changed files with 676 additions and 115 deletions
+41 -17
View File
@@ -70,26 +70,50 @@ type subscription struct {
// the stream closes, or ctx is cancelled.
// It returns all events from nextEventID to the current tail.
func (sub *subscription) Next(ctx context.Context, nextEventID int64) ([]SSEEvent, bool, error) {
sub.st.mu.Lock()
defer sub.st.mu.Unlock()
st := sub.st
for int64(len(sub.st.events)) <= nextEventID && !sub.st.closed {
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
sub.st.cond.Broadcast()
case <-done:
}
}()
sub.st.cond.Wait()
close(done)
if ctx.Err() != nil {
return nil, false, ctx.Err()
// Launch a single watcher that observes ctx cancellation and wakes any
// waiter. It broadcasts under the lock so the signal cannot be missed by a
// waiter that has not yet parked on cond.Wait(); the predicate loop below
// re-checks ctx.Err() after every wake, so a broadcast delivered before a
// waiter parks is still observed on the next predicate check.
//
// done is closed (under the lock) when Next returns, which makes the watcher
// exit even if ctx never cancels — so neither goroutine can leak.
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
st.mu.Lock()
st.cond.Broadcast()
st.mu.Unlock()
case <-done:
}
}()
st.mu.Lock()
defer func() {
// Signal the watcher to exit, then release the lock. Closing done under
// the lock pairs with the watcher's Broadcast-under-lock so there is no
// window in which the watcher broadcasts to an unlocked, already-returned
// Next.
close(done)
st.mu.Unlock()
}()
// Wait until there is data at/after nextEventID, the stream closes, or ctx
// is cancelled. Each predicate component is re-checked after every wake so a
// missed/early broadcast cannot wedge the wait.
for int64(len(st.events)) <= nextEventID && !st.closed && ctx.Err() == nil {
st.cond.Wait()
}
out := append([]SSEEvent(nil), sub.st.events[nextEventID:]...)
return out, sub.st.closed, nil
if int64(len(st.events)) <= nextEventID && !st.closed && ctx.Err() != nil {
return nil, false, ctx.Err()
}
out := append([]SSEEvent(nil), st.events[nextEventID:]...)
return out, st.closed, nil
}
// streamerHub manages all in-flight streamers.