@@ -5,17 +5,19 @@ import "sync"
55// baton is a channel-based mutex. This allows for using it as part of a select
66// statement.
77type baton struct {
8- mu sync.Mutex
9- holder string
10- acquire map [ string ] chan struct {}
8+ mu sync.Mutex
9+ holder string
10+ ch chan struct {}
1111}
1212
1313// newBaton creates a new baton for sharing among actors, each identified
14- // by a non-empty string.
14+ // by a non-empty string. The baton is initially not held by anything.
1515func newBaton () * baton {
16- return & baton {
17- acquire : make (map [ string ] chan struct {}),
16+ b := & baton {
17+ ch : make (chan struct {}, 1 ),
1818 }
19+ b .ch <- struct {}{}
20+ return b
1921}
2022
2123// HeldBy reports if the actor specified by the argument holds the baton.
@@ -26,61 +28,45 @@ func (b *baton) HeldBy(by string) bool {
2628}
2729
2830// Acquire returns a channel that receives when the baton is acquired by the
29- // acquirer.
30- func (b * baton ) Acquire (by string ) <- chan struct {} {
31+ // caller.
32+ // Be sure to call [baton.Acquired] after receiving from the channel, e.g.
33+ //
34+ // select {
35+ // case <-bat.Acquire():
36+ // bat.Acquired("me")
37+ // defer bat.Release("me")
38+ // ...
39+ // }
40+ func (b * baton ) Acquire () <- chan struct {} {
41+ // b.ch should never change, so no need to lock around it
42+ return b .ch
43+ }
44+
45+ // Acquired must be called by the actor that successfully acquired the baton
46+ // immediately after acquiring it.
47+ // It panics if the baton is already marked as held.
48+ // It is necessary to separate this from [baton.Acquire] because it is practically
49+ // impossible to reliably and atomically pass the baton and record the new holder
50+ // at the same time without deadlocks.
51+ func (b * baton ) Acquired (by string ) {
3152 b .mu .Lock ()
3253 defer b .mu .Unlock ()
33-
34- // If there's an existing channel for this actor, reuse it.
35- ch := b .acquire [by ]
36- if ch == nil {
37- ch = make (chan struct {})
38- }
39-
40- // If nothing holds the baton currently, assign it to the caller.
41- // The caller won't be receiving on the channel until after we
42- // return it, so make the channel receivable by closing it.
43- if b .holder == "" {
44- b .holder = by
45- close (ch )
46- delete (b .acquire , by ) // in case it is in the map
47- return ch
54+ if b .holder != "" {
55+ // panic is not ideal for a few reasons (a fatal log might be better),
56+ // but keeps baton focused on being a concurrency primitive. As long as
57+ // the panic reaches the Go runtime, Go will give us a traceback and exit.
58+ panic ("baton already held by " + b .holder )
4859 }
49-
50- // Something holds the baton, so record that this actor is
51- // waiting for the baton.
52- b .acquire [by ] = ch
53- return ch
60+ b .holder = by
5461}
5562
5663// Release releases the baton, if it is held by the argument.
5764func (b * baton ) Release (by string ) {
5865 b .mu .Lock ()
5966 defer b .mu .Unlock ()
60-
61- // Only release if its the same actor, to prevent bugs due
62- // to double-releasing.
6367 if b .holder != by {
6468 return
6569 }
66-
67- // Attempt to pass the baton to anything still waiting for it.
68- for a , ch := range b .acquire {
69- delete (b .acquire , a )
70- select {
71- case ch <- struct {}{}:
72- // We were able to send a value to the channel,
73- // so this actor was still waiting to receive.
74- // Therefore this actor has acquired the baton.
75- b .holder = a
76- return
77- default :
78- // This actor has stopped waiting to receive,
79- // so try another.
80- }
81- }
82-
83- // Nothing was still waiting on its channel,
84- // so now nothing holds the baton.
8570 b .holder = ""
71+ b .ch <- struct {}{}
8672}
0 commit comments