@@ -697,7 +697,8 @@ func (c *StoreReconciler) createPrebuiltWorkspace(ctx context.Context, prebuiltW
697697return xerrors .Errorf ("failed to generate unique prebuild ID: %w" ,err )
698698}
699699
700- return c .store .InTx (func (db database.Store )error {
700+ var provisionerJob * database.ProvisionerJob
701+ err = c .store .InTx (func (db database.Store )error {
701702template ,err := db .GetTemplateByID (ctx ,templateID )
702703if err != nil {
703704return xerrors .Errorf ("failed to get template: %w" ,err )
@@ -732,11 +733,20 @@ func (c *StoreReconciler) createPrebuiltWorkspace(ctx context.Context, prebuiltW
732733c .logger .Info (ctx ,"attempting to create prebuild" ,slog .F ("name" ,name ),
733734slog .F ("workspace_id" ,prebuiltWorkspaceID .String ()),slog .F ("preset_id" ,presetID .String ()))
734735
735- return c .provision (ctx ,db ,prebuiltWorkspaceID ,template ,presetID ,database .WorkspaceTransitionStart ,workspace ,DeprovisionModeNormal )
736+ provisionerJob ,err = c .provision (ctx ,db ,prebuiltWorkspaceID ,template ,presetID ,database .WorkspaceTransitionStart ,workspace ,DeprovisionModeNormal )
737+ return err
736738},& database.TxOptions {
737739Isolation :sql .LevelRepeatableRead ,
738740ReadOnly :false ,
739741})
742+ if err != nil {
743+ return err
744+ }
745+
746+ // Publish provisioner job event to notify the acquirer that a new job was posted
747+ c .publishProvisionerJob (ctx ,provisionerJob ,prebuiltWorkspaceID )
748+
749+ return nil
740750}
741751
742752// provisionDelete provisions a delete transition for a prebuilt workspace.
@@ -748,26 +758,25 @@ func (c *StoreReconciler) createPrebuiltWorkspace(ctx context.Context, prebuiltW
748758//
749759// IMPORTANT: This function must be called within a database transaction. It does not create its own transaction.
750760// The caller is responsible for managing the transaction boundary via db.InTx().
751- func (c * StoreReconciler )provisionDelete (ctx context.Context ,db database.Store ,workspaceID uuid.UUID ,templateID uuid.UUID ,presetID uuid.UUID ,mode DeprovisionMode )error {
761+ func (c * StoreReconciler )provisionDelete (ctx context.Context ,db database.Store ,workspaceID uuid.UUID ,templateID uuid.UUID ,presetID uuid.UUID ,mode DeprovisionMode )( * database. ProvisionerJob , error ) {
752762workspace ,err := db .GetWorkspaceByID (ctx ,workspaceID )
753763if err != nil {
754- return xerrors .Errorf ("get workspace by ID: %w" ,err )
764+ return nil , xerrors .Errorf ("get workspace by ID: %w" ,err )
755765}
756766
757767template ,err := db .GetTemplateByID (ctx ,templateID )
758768if err != nil {
759- return xerrors .Errorf ("failed to get template: %w" ,err )
769+ return nil , xerrors .Errorf ("failed to get template: %w" ,err )
760770}
761771
762772if workspace .OwnerID != database .PrebuildsSystemUserID {
763- return xerrors .Errorf ("prebuilt workspace is not owned by prebuild user anymore, probably it was claimed" )
773+ return nil , xerrors .Errorf ("prebuilt workspace is not owned by prebuild user anymore, probably it was claimed" )
764774}
765775
766776c .logger .Info (ctx ,"attempting to delete prebuild" ,slog .F ("orphan" ,mode .String ()),
767777slog .F ("name" ,workspace .Name ),slog .F ("workspace_id" ,workspaceID .String ()),slog .F ("preset_id" ,presetID .String ()))
768778
769- return c .provision (ctx ,db ,workspaceID ,template ,presetID ,
770- database .WorkspaceTransitionDelete ,workspace ,mode )
779+ return c .provision (ctx ,db ,workspaceID ,template ,presetID ,database .WorkspaceTransitionDelete ,workspace ,mode )
771780}
772781
773782// cancelAndOrphanDeletePendingPrebuilds cancels pending prebuild jobs from inactive template versions
@@ -779,7 +788,8 @@ func (c *StoreReconciler) provisionDelete(ctx context.Context, db database.Store
779788// Since these jobs were never processed by a provisioner, no Terraform resources were created,
780789// making it safe to orphan-delete the workspaces (skipping Terraform destroy).
781790func (c * StoreReconciler )cancelAndOrphanDeletePendingPrebuilds (ctx context.Context ,templateID uuid.UUID ,templateVersionID uuid.UUID ,presetID uuid.UUID )error {
782- return c .store .InTx (func (db database.Store )error {
791+ provisionerJobs := make (map [uuid.UUID ]* database.ProvisionerJob )
792+ err := c .store .InTx (func (db database.Store )error {
783793canceledJobs ,err := db .UpdatePrebuildProvisionerJobWithCancel (
784794ctx ,
785795database.UpdatePrebuildProvisionerJobWithCancelParams {
@@ -808,11 +818,13 @@ func (c *StoreReconciler) cancelAndOrphanDeletePendingPrebuilds(ctx context.Cont
808818
809819var multiErr multierror.Error
810820for _ ,job := range canceledJobs {
811- err = c .provisionDelete (ctx ,db ,job .WorkspaceID ,job .TemplateID ,presetID ,DeprovisionModeOrphan )
821+ provisionerJob , err : =c .provisionDelete (ctx ,db ,job .WorkspaceID ,job .TemplateID ,presetID ,DeprovisionModeOrphan )
812822if err != nil {
813823c .logger .Error (ctx ,"failed to orphan delete canceled prebuild" ,
814824slog .F ("workspace_id" ,job .WorkspaceID .String ()),slog .Error (err ))
815825multiErr .Errors = append (multiErr .Errors ,err )
826+ }else {
827+ provisionerJobs [job .WorkspaceID ]= provisionerJob
816828}
817829}
818830
@@ -821,15 +833,35 @@ func (c *StoreReconciler) cancelAndOrphanDeletePendingPrebuilds(ctx context.Cont
821833Isolation :sql .LevelRepeatableRead ,
822834ReadOnly :false ,
823835})
836+ if err != nil {
837+ return err
838+ }
839+
840+ // Publish provisioner job events to notify the acquirer that new jobs were posted
841+ for workspaceID ,job := range provisionerJobs {
842+ c .publishProvisionerJob (ctx ,job ,workspaceID )
843+ }
844+
845+ return nil
824846}
825847
826848func (c * StoreReconciler )deletePrebuiltWorkspace (ctx context.Context ,prebuiltWorkspaceID uuid.UUID ,templateID uuid.UUID ,presetID uuid.UUID )error {
827- return c .store .InTx (func (db database.Store )error {
828- return c .provisionDelete (ctx ,db ,prebuiltWorkspaceID ,templateID ,presetID ,DeprovisionModeNormal )
849+ var provisionerJob * database.ProvisionerJob
850+ err := c .store .InTx (func (db database.Store ) (err error ) {
851+ provisionerJob ,err = c .provisionDelete (ctx ,db ,prebuiltWorkspaceID ,templateID ,presetID ,DeprovisionModeNormal )
852+ return err
829853},& database.TxOptions {
830854Isolation :sql .LevelRepeatableRead ,
831855ReadOnly :false ,
832856})
857+ if err != nil {
858+ return err
859+ }
860+
861+ // Publish provisioner job event to notify the acquirer that a new job was posted
862+ c .publishProvisionerJob (ctx ,provisionerJob ,prebuiltWorkspaceID )
863+
864+ return nil
833865}
834866
835867func (c * StoreReconciler )provision (
@@ -841,10 +873,10 @@ func (c *StoreReconciler) provision(
841873transition database.WorkspaceTransition ,
842874workspace database.Workspace ,
843875mode DeprovisionMode ,
844- )error {
876+ )( * database. ProvisionerJob , error ) {
845877tvp ,err := db .GetPresetParametersByTemplateVersionID (ctx ,template .ActiveVersionID )
846878if err != nil {
847- return xerrors .Errorf ("fetch preset details: %w" ,err )
879+ return nil , xerrors .Errorf ("fetch preset details: %w" ,err )
848880}
849881
850882var params []codersdk.WorkspaceBuildParameter
@@ -893,26 +925,34 @@ func (c *StoreReconciler) provision(
893925audit.WorkspaceBuildBaggage {},
894926)
895927if err != nil {
896- return xerrors .Errorf ("provision workspace: %w" ,err )
928+ return nil , xerrors .Errorf ("provision workspace: %w" ,err )
897929}
898-
899930if provisionerJob == nil {
900- return nil
901- }
902-
903- // Publish provisioner job event outside of transaction.
904- select {
905- case c .provisionNotifyCh <- * provisionerJob :
906- default :// channel full, drop the message; provisioner will pick this job up later with its periodic check, though.
907- c .logger .Warn (ctx ,"provisioner job notification queue full, dropping" ,
908- slog .F ("job_id" ,provisionerJob .ID ),slog .F ("prebuild_id" ,prebuildID .String ()))
931+ // This should not happen, builder.Build() should either return a job or an error.
932+ // Returning an error to fail fast if we hit this unexpected case.
933+ return nil ,xerrors .Errorf ("provision succeeded but returned no job" )
909934}
910935
911936c .logger .Info (ctx ,"prebuild job scheduled" ,slog .F ("transition" ,transition ),
912937slog .F ("prebuild_id" ,prebuildID .String ()),slog .F ("preset_id" ,presetID .String ()),
913938slog .F ("job_id" ,provisionerJob .ID ))
914939
915- return nil
940+ return provisionerJob ,nil
941+ }
942+
943+ // publishProvisionerJob publishes a provisioner job event to notify the acquirer that a new job has been created.
944+ // This must be called after the database transaction that creates the job has committed to ensure
945+ // the job is visible to provisioners when they query the database.
946+ func (c * StoreReconciler )publishProvisionerJob (ctx context.Context ,provisionerJob * database.ProvisionerJob ,workspaceID uuid.UUID ) {
947+ if provisionerJob == nil {
948+ return
949+ }
950+ select {
951+ case c .provisionNotifyCh <- * provisionerJob :
952+ default :// channel full, drop the message; provisioner will pick this job up later with its periodic check
953+ c .logger .Warn (ctx ,"provisioner job notification queue full, dropping" ,
954+ slog .F ("job_id" ,provisionerJob .ID ),slog .F ("prebuild_id" ,workspaceID .String ()))
955+ }
916956}
917957
918958// ForceMetricsUpdate forces the metrics collector, if defined, to update its state (we cache the metrics state to