@@ -16,11 +16,14 @@ import (
16
16
"github.com/coder/coder/v2/coderd/autobuild"
17
17
"github.com/coder/coder/v2/coderd/coderdtest"
18
18
"github.com/coder/coder/v2/coderd/database"
19
+ "github.com/coder/coder/v2/coderd/database/dbtestutil"
19
20
"github.com/coder/coder/v2/coderd/rbac"
20
21
agplschedule "github.com/coder/coder/v2/coderd/schedule"
21
22
"github.com/coder/coder/v2/coderd/schedule/cron"
22
23
"github.com/coder/coder/v2/coderd/util/ptr"
23
24
"github.com/coder/coder/v2/codersdk"
25
+ entaudit "github.com/coder/coder/v2/enterprise/audit"
26
+ "github.com/coder/coder/v2/enterprise/audit/backends"
24
27
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
25
28
"github.com/coder/coder/v2/enterprise/coderd/license"
26
29
"github.com/coder/coder/v2/enterprise/coderd/schedule"
@@ -309,6 +312,84 @@ func TestWorkspaceAutobuild(t *testing.T) {
309
312
require .True (t , ws .LastUsedAt .After (lastUsedAt ))
310
313
})
311
314
315
+ // This test serves as a regression prevention for generating
316
+ // audit logs in the same transaction the transition workspaces to
317
+ // the dormant state. The auditor that is passed to autobuild does
318
+ // not use the transaction when inserting an audit log which can
319
+ // cause a deadlock.
320
+ t .Run ("NoDeadlock" , func (t * testing.T ) {
321
+ t .Parallel ()
322
+
323
+ if ! dbtestutil .WillUsePostgres () {
324
+ t .Skipf ("Skipping non-postgres run" )
325
+ }
326
+
327
+ var (
328
+ ticker = make (chan time.Time )
329
+ statCh = make (chan autobuild.Stats )
330
+ inactiveTTL = time .Minute
331
+ )
332
+
333
+ const (
334
+ maxConns = 3
335
+ numWorkspaces = maxConns * 5
336
+ )
337
+ // This is a bit bizarre but necessary so that we can
338
+ // initialize our coderd with a real auditor and limit DB connections
339
+ // to simulate deadlock conditions.
340
+ db , pubsub , sdb := dbtestutil .NewDBWithSQLDB (t )
341
+ // Set MaxOpenConns so we can ensure we aren't inadvertently acquiring
342
+ // another connection from within a transaction.
343
+ sdb .SetMaxOpenConns (maxConns )
344
+ auditor := entaudit .NewAuditor (db , entaudit .DefaultFilter , backends .NewPostgres (db , true ))
345
+
346
+ client , user := coderdenttest .New (t , & coderdenttest.Options {
347
+ Options : & coderdtest.Options {
348
+ AutobuildTicker : ticker ,
349
+ AutobuildStats : statCh ,
350
+ TemplateScheduleStore : schedule .NewEnterpriseTemplateScheduleStore (agplUserQuietHoursScheduleStore ()),
351
+ Database : db ,
352
+ Pubsub : pubsub ,
353
+ Auditor : auditor ,
354
+ IncludeProvisionerDaemon : true ,
355
+ },
356
+ LicenseOptions : & coderdenttest.LicenseOptions {
357
+ Features : license.Features {codersdk .FeatureAdvancedTemplateScheduling : 1 },
358
+ },
359
+ })
360
+
361
+ version := coderdtest .CreateTemplateVersion (t , client , user .OrganizationID , & echo.Responses {
362
+ Parse : echo .ParseComplete ,
363
+ ProvisionPlan : echo .PlanComplete ,
364
+ ProvisionApply : echo .ApplyComplete ,
365
+ })
366
+ template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version .ID , func (ctr * codersdk.CreateTemplateRequest ) {
367
+ ctr .TimeTilDormantMillis = ptr.Ref [int64 ](inactiveTTL .Milliseconds ())
368
+ })
369
+ coderdtest .AwaitTemplateVersionJobCompleted (t , client , version .ID )
370
+
371
+ workspaces := make ([]codersdk.Workspace , 0 , numWorkspaces )
372
+ for i := 0 ; i < numWorkspaces ; i ++ {
373
+ ws := coderdtest .CreateWorkspace (t , client , user .OrganizationID , template .ID )
374
+ build := coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , ws .LatestBuild .ID )
375
+ require .Equal (t , codersdk .WorkspaceStatusRunning , build .Status )
376
+ workspaces = append (workspaces , ws )
377
+ }
378
+
379
+ // Simulate being inactive.
380
+ ticker <- time .Now ().Add (time .Hour )
381
+ stats := <- statCh
382
+
383
+ // Expect workspace to transition to stopped state for breaching
384
+ // failure TTL.
385
+ require .Len (t , stats .Transitions , numWorkspaces )
386
+ for _ , ws := range workspaces {
387
+ // The workspace should be dormant.
388
+ ws = coderdtest .MustWorkspace (t , client , ws .ID )
389
+ require .NotNil (t , ws .DormantAt )
390
+ }
391
+ })
392
+
312
393
t .Run ("InactiveTTLTooEarly" , func (t * testing.T ) {
313
394
t .Parallel ()
314
395
0 commit comments