@@ -2,13 +2,20 @@ package proxy
22
33import (
44 "context"
5+ "time"
56
67 "resenje.org/singleflight"
78
89 "github.com/authzed/spicedb/pkg/datastore"
910 "github.com/authzed/spicedb/pkg/datastore/options"
1011)
1112
13+ // singleflightTimeout is the maximum time that a caller of a singleflighted method
14+ // will wait before giving up on the singleflight executor.
15+ // This prevents a possible deadlock when all datastore connections are held by goroutines waiting on the
16+ // singleflight while the singleflight executor is blocked waiting for a connection.
17+ const singleFlightTimeout = 1 * time .Second
18+
1219// NewSingleflightDatastoreProxy creates a new Datastore proxy which
1320// deduplicates calls to Datastore methods that can share results.
1421func NewSingleflightDatastoreProxy (d datastore.Datastore ) datastore.Datastore {
@@ -19,6 +26,7 @@ type singleflightProxy struct {
1926 headRevGroup singleflight.Group [string , datastore.RevisionWithSchemaHash ]
2027 checkRevGroup singleflight.Group [string , string ]
2128 statsGroup singleflight.Group [string , datastore.Stats ]
29+ featuresGroup singleflight.Group [string , * datastore.Features ]
2230 delegate datastore.Datastore
2331}
2432
@@ -43,17 +51,31 @@ func (p *singleflightProxy) ReadWriteTx(ctx context.Context, f datastore.TxUserF
4351func (p * singleflightProxy ) OptimizedRevision (ctx context.Context ) (datastore.RevisionWithSchemaHash , error ) {
4452 // NOTE: Optimized revisions are singleflighted by the underlying datastore via the
4553 // CachedOptimizedRevisions struct.
54+ ctx , span := tracer .Start (ctx , "singleflightProxy.OptimizedRevision" )
55+ defer span .End ()
4656 return p .delegate .OptimizedRevision (ctx )
4757}
4858
4959func (p * singleflightProxy ) CheckRevision (ctx context.Context , revision datastore.Revision ) error {
60+ ctx , span := tracer .Start (ctx , "singleflightProxy.CheckRevision" )
61+ defer span .End ()
62+
63+ ctx , cancel := context .WithTimeout (ctx , singleFlightTimeout )
64+ defer cancel ()
65+
5066 _ , _ , err := p .checkRevGroup .Do (ctx , revision .String (), func (ctx context.Context ) (string , error ) {
5167 return "" , p .delegate .CheckRevision (ctx , revision )
5268 })
5369 return err
5470}
5571
5672func (p * singleflightProxy ) HeadRevision (ctx context.Context ) (datastore.RevisionWithSchemaHash , error ) {
73+ ctx , span := tracer .Start (ctx , "singleflightProxy.HeadRevision" )
74+ defer span .End ()
75+
76+ ctx , cancel := context .WithTimeout (ctx , singleFlightTimeout )
77+ defer cancel ()
78+
5779 rev , _ , err := p .headRevGroup .Do (ctx , "" , func (ctx context.Context ) (datastore.RevisionWithSchemaHash , error ) {
5880 return p .delegate .HeadRevision (ctx )
5981 })
@@ -69,14 +91,29 @@ func (p *singleflightProxy) Watch(ctx context.Context, afterRevision datastore.R
6991}
7092
7193func (p * singleflightProxy ) Statistics (ctx context.Context ) (datastore.Stats , error ) {
94+ ctx , span := tracer .Start (ctx , "singleflightProxy.Statistics" )
95+ defer span .End ()
96+
97+ ctx , cancel := context .WithTimeout (ctx , singleFlightTimeout )
98+ defer cancel ()
99+
72100 stats , _ , err := p .statsGroup .Do (ctx , "" , func (ctx context.Context ) (datastore.Stats , error ) {
73101 return p .delegate .Statistics (ctx )
74102 })
75103 return stats , err
76104}
77105
78106func (p * singleflightProxy ) Features (ctx context.Context ) (* datastore.Features , error ) {
79- return p .delegate .Features (ctx )
107+ ctx , span := tracer .Start (ctx , "singleflightProxy.Statistics" )
108+ defer span .End ()
109+
110+ ctx , cancel := context .WithTimeout (ctx , singleFlightTimeout )
111+ defer cancel ()
112+
113+ features , _ , err := p .featuresGroup .Do (ctx , "" , func (ctx context.Context ) (* datastore.Features , error ) {
114+ return p .delegate .Features (ctx )
115+ })
116+ return features , err
80117}
81118
82119func (p * singleflightProxy ) OfflineFeatures () (* datastore.Features , error ) {
0 commit comments