Skip to content

Commit 0d60656

Browse files
committed
use the old SetFinalizer instead of AddCleanup
Standard library functions of newer go versions can't be used without also bumping the go.mod go version, so switched PubSub to the old runtime.SetFinalizer() instead of the go1.24+ runtime.AddCleanup(), and added a test to make sure it works correctly.
1 parent 687dbb7 commit 0d60656

File tree

3 files changed

+42
-20
lines changed

3 files changed

+42
-20
lines changed

go.mod

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
module github.com/koonix/go-livereload
22

3-
// Require 1.22+ to benefit from the for-loop changes.
4-
// https://tip.golang.org/doc/go1.22#language
53
go 1.22
64

7-
// Use 1.24+ so we can use the runtime.AddCleanup function.
8-
// https://tip.golang.org/doc/go1.24#improved-finalizers
9-
toolchain go1.24.0
10-
115
require golang.org/x/net v0.35.0

internal/pubsub/pubsub.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,23 @@ type sub[T any] struct {
2626
}
2727

2828
func New[T any]() *PubSub[T] {
29+
2930
p := &PubSub[T]{
3031
msg: make(chan T),
3132
addSub: make(chan *sub[T]),
3233
removeSub: make(chan *sub[T]),
3334
done: make(chan struct{}),
3435
}
35-
cleanup := runtime.AddCleanup(p, func(ch chan struct{}) { close(ch) }, p.done)
36+
runtime.SetFinalizer(p, func(p *PubSub[T]) {
37+
p.Close()
38+
})
39+
40+
msg := p.msg
41+
addSub := p.addSub
42+
removeSub := p.removeSub
43+
done := p.done
44+
3645
go func() {
37-
cleanup.Stop()
3846
subs := make(map[*sub[T]]struct{})
3947
defer func() {
4048
for sub := range subs {
@@ -43,14 +51,14 @@ func New[T any]() *PubSub[T] {
4351
}()
4452
for {
4553
select {
46-
case <-p.done:
54+
case <-done:
4755
return
48-
case sub := <-p.addSub:
56+
case sub := <-addSub:
4957
subs[sub] = struct{}{}
50-
case sub := <-p.removeSub:
58+
case sub := <-removeSub:
5159
delete(subs, sub)
5260
close(sub.msg)
53-
case msg := <-p.msg:
61+
case msg := <-msg:
5462
wg := new(sync.WaitGroup)
5563
wg.Add(len(subs))
5664
for sub := range subs {
@@ -66,18 +74,22 @@ func New[T any]() *PubSub[T] {
6674
}
6775
}
6876
}()
77+
6978
return p
7079
}
7180

7281
func (p *PubSub[T]) Subscribe() (msg <-chan T, unsubscribe func()) {
82+
7383
sub := &sub[T]{
7484
msg: make(chan T, 1),
7585
done: make(chan struct{}),
7686
}
87+
7788
select {
7889
case p.addSub <- sub:
7990
case <-p.done:
8091
}
92+
8193
unsub := func() {
8294
sub.once.Do(func() {
8395
close(sub.done)
@@ -87,6 +99,7 @@ func (p *PubSub[T]) Subscribe() (msg <-chan T, unsubscribe func()) {
8799
}
88100
})
89101
}
102+
90103
return sub.msg, unsub
91104
}
92105

internal/pubsub/pubsub_test.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
// Copyright 2024 the go-livereload authors.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
package pubsub_test
4+
package pubsub
55

66
import (
7-
"fmt"
7+
"runtime"
88
"sync"
99
"sync/atomic"
1010
"testing"
11-
12-
"github.com/koonix/go-livereload/internal/pubsub"
11+
"time"
1312
)
1413

1514
func TestPubSub(t *testing.T) {
1615

17-
ps := pubsub.New[string]()
16+
ps := New[string]()
1817
wg := new(sync.WaitGroup)
1918
rcvCount := new(atomic.Int64)
2019
msg := "hello world"
@@ -31,7 +30,6 @@ func TestPubSub(t *testing.T) {
3130
t.Errorf("ch1 got incorrect message; want %q, got %q", msg, m)
3231
}
3332
}
34-
fmt.Println("1")
3533
}()
3634

3735
ch2, unsub2 := ps.Subscribe()
@@ -46,7 +44,6 @@ func TestPubSub(t *testing.T) {
4644
t.Errorf("ch2 got incorrect message; want %q, got %q", msg, m)
4745
}
4846
}
49-
fmt.Println("2")
5047
}()
5148

5249
ch3, unsub3 := ps.Subscribe()
@@ -58,7 +55,6 @@ func TestPubSub(t *testing.T) {
5855
for range ch3 {
5956
t.Errorf("ch3 got message, didn't expect any")
6057
}
61-
fmt.Println("3")
6258
}()
6359

6460
ps.Publish(msg)
@@ -71,3 +67,22 @@ func TestPubSub(t *testing.T) {
7167
t.Errorf("incorrect message count; want %d, got %d", want, got)
7268
}
7369
}
70+
71+
func TestPubSubFinalizer(t *testing.T) {
72+
73+
// Create the PubSub inside an inner scope
74+
// so the pointer becomes unreachable when we leave the block.
75+
done := func() <-chan struct{} {
76+
p := New[int]()
77+
return p.done
78+
}()
79+
80+
runtime.GC()
81+
82+
select {
83+
case <-done:
84+
// Success.
85+
case <-time.After(500 * time.Millisecond):
86+
t.Fatal("finalizer did not run")
87+
}
88+
}

0 commit comments

Comments
 (0)