@@ -86,6 +86,8 @@ type Options struct {
86
86
PrometheusRegistry * prometheus.Registry
87
87
ReportMetadataInterval time.Duration
88
88
ServiceBannerRefreshInterval time.Duration
89
+ ExperimentRefreshInterval time.Duration
90
+ FetchExperiments func (ctx context.Context ) (codersdk.Experiments , error )
89
91
Syscaller agentproc.Syscaller
90
92
// ModifiedProcesses is used for testing process priority management.
91
93
ModifiedProcesses chan []* agentproc.Process
@@ -134,6 +136,14 @@ func New(options Options) Agent {
134
136
return "" , nil
135
137
}
136
138
}
139
+ if options .FetchExperiments == nil {
140
+ options .FetchExperiments = func (ctx context.Context ) (codersdk.Experiments , error ) {
141
+ return codersdk.Experiments {}, nil
142
+ }
143
+ }
144
+ if options .ExperimentRefreshInterval == 0 {
145
+ options .ExperimentRefreshInterval = 5 * time .Minute
146
+ }
137
147
if options .ReportMetadataInterval == 0 {
138
148
options .ReportMetadataInterval = time .Second
139
149
}
@@ -167,6 +177,7 @@ func New(options Options) Agent {
167
177
environmentVariables : options .EnvironmentVariables ,
168
178
client : options .Client ,
169
179
exchangeToken : options .ExchangeToken ,
180
+ fetchExperiments : options .FetchExperiments ,
170
181
filesystem : options .Filesystem ,
171
182
logDir : options .LogDir ,
172
183
tempDir : options .TempDir ,
@@ -249,6 +260,10 @@ type agent struct {
249
260
lifecycleStates []agentsdk.PostLifecycleRequest
250
261
lifecycleLastReportedIndex int // Keeps track of the last lifecycle state we successfully reported.
251
262
263
+ fetchExperiments func (ctx context.Context ) (codersdk.Experiments , error )
264
+ fetchExperimentsInterval time.Duration
265
+ experiments atomic.Pointer [codersdk.Experiments ]
266
+
252
267
network * tailnet.Conn
253
268
addresses []netip.Prefix
254
269
statsReporter * statsReporter
@@ -737,6 +752,28 @@ func (a *agent) fetchServiceBannerLoop(ctx context.Context, conn drpc.Conn) erro
737
752
}
738
753
}
739
754
755
+ // fetchExperimentsLoop fetches experiments on an interval.
756
+ func (a * agent ) fetchExperimentsLoop (ctx context.Context ) error {
757
+ ticker := time .NewTicker (a .fetchExperimentsInterval )
758
+ defer ticker .Stop ()
759
+ for {
760
+ select {
761
+ case <- ctx .Done ():
762
+ return ctx .Err ()
763
+ case <- ticker .C :
764
+ experiments , err := a .fetchExperiments (ctx )
765
+ if err != nil {
766
+ if ctx .Err () != nil {
767
+ return ctx .Err ()
768
+ }
769
+ a .logger .Error (ctx , "failed to update experiments" , slog .Error (err ))
770
+ return err
771
+ }
772
+ a .experiments .Store (& experiments )
773
+ }
774
+ }
775
+ }
776
+
740
777
func (a * agent ) run () (retErr error ) {
741
778
// This allows the agent to refresh it's token if necessary.
742
779
// For instance identity this is required, since the instance
@@ -747,6 +784,12 @@ func (a *agent) run() (retErr error) {
747
784
}
748
785
a .sessionToken .Store (& sessionToken )
749
786
787
+ exp , err := a .fetchExperiments (a .hardCtx )
788
+ if err != nil {
789
+ return xerrors .Errorf ("fetch experiments: %w" , err )
790
+ }
791
+ a .experiments .Store (& exp )
792
+
750
793
// ConnectRPC returns the dRPC connection we use for the Agent and Tailnet v2+ APIs
751
794
conn , err := a .client .ConnectRPC (a .hardCtx )
752
795
if err != nil {
@@ -856,6 +899,10 @@ func (a *agent) run() (retErr error) {
856
899
857
900
connMan .start ("fetch service banner loop" , gracefulShutdownBehaviorStop , a .fetchServiceBannerLoop )
858
901
902
+ connMan .start ("fetch experiments loop" , gracefulShutdownBehaviorStop , func (ctx context.Context , _ drpc.Conn ) error {
903
+ return a .fetchExperimentsLoop (ctx )
904
+ })
905
+
859
906
connMan .start ("stats report loop" , gracefulShutdownBehaviorStop , func (ctx context.Context , conn drpc.Conn ) error {
860
907
if err := networkOK .wait (ctx ); err != nil {
861
908
return xerrors .Errorf ("no network: %w" , err )
0 commit comments