diff --git a/common/build.go b/common/build.go index ef5dda01..73e7972e 100644 --- a/common/build.go +++ b/common/build.go @@ -656,11 +656,8 @@ func (b *Build) executeStage(ctx context.Context, buildStage BuildStage, executo // rather than the returned error because gRPC wraps deadline // exceeded as a status error that does not unwrap to // context.DeadlineExceeded. - if err != nil && errors.Is(ctx.Err(), context.DeadlineExceeded) { - b.logger.Warningln( - string(buildStage) + " could not run to completion because the timeout was exceeded. " + - "For more control over job and script timeouts see: " + - "https://docs.gitlab.com/ci/runners/configure_runners/#set-script-and-after_script-timeouts") + if err != nil { + b.warnTimeoutExceeded(ctx, string(buildStage)) } return err } @@ -676,14 +673,7 @@ func (b *Build) executeStage(ctx context.Context, buildStage BuildStage, executo b.setCurrentStage(buildStage) b.Log().WithField("build_stage", buildStage).Debug("Executing build stage") - defer func() { - if errors.Is(ctx.Err(), context.DeadlineExceeded) { - b.logger.Warningln( - string(buildStage) + " could not run to completion because the timeout was exceeded. " + - "For more control over job and script timeouts see: " + - "https://docs.gitlab.com/ci/runners/configure_runners/#set-script-and-after_script-timeouts") - } - }() + defer b.warnTimeoutExceeded(ctx, string(buildStage)) shell := executor.Shell() if shell == nil { @@ -828,6 +818,12 @@ func (b *Build) executeScript(ctx context.Context, trace JobTrace, executor Exec //nolint:errcheck err = b.executeStepStage(ctx, executor.(steps.Connector), "concrete", concreteSteps, trace.SetCancelFunc) + // Whole-job dispatch: stage breakdown isn't visible to the user, so + // attribute the timeout to "Job" rather than a build stage. + if err != nil { + b.warnTimeoutExceeded(ctx, "Job") + } + b.executeUploadReferees(ctx, startTime, time.Now()) return err @@ -1092,6 +1088,16 @@ func (b *Build) attemptExecuteStage( return err } +func (b *Build) warnTimeoutExceeded(ctx context.Context, subject string) { + if !errors.Is(ctx.Err(), context.DeadlineExceeded) { + return + } + b.logger.Warningln( + subject + " could not run to completion because the timeout was exceeded. " + + "For more control over job and script timeouts see: " + + "https://docs.gitlab.com/ci/runners/configure_runners/#set-script-and-after_script-timeouts") +} + func (b *Build) GetBuildTimeout() time.Duration { buildTimeout := b.RunnerInfo.Timeout if buildTimeout <= 0 { diff --git a/functions/concrete/builder/builder.go b/functions/concrete/builder/builder.go index 310ae0fd..30fba029 100644 --- a/functions/concrete/builder/builder.go +++ b/functions/concrete/builder/builder.go @@ -353,7 +353,6 @@ func (b *builder) buildSteps() []stages.Step { }) if step.Name == spec.StepNameAfterScript { - s.AllowFailure = true s.OnSuccess = true s.OnFailure = true afterScript = append(afterScript, s) diff --git a/functions/concrete/run/runner.go b/functions/concrete/run/runner.go index d17b3d62..cbfc5c64 100644 --- a/functions/concrete/run/runner.go +++ b/functions/concrete/run/runner.go @@ -224,17 +224,21 @@ func (r *Runner) runScriptSteps(jobCtx context.Context, steps []stages.Step) err r.setCancel(cancel) var firstErr error + timeoutStage := "step_script" for _, step := range steps { r.loadGitlabEnv() - if err := r.section(scriptCtx, "step_"+step.Step, step.Run); err != nil { + sectionName := "step_" + step.Step + if err := r.section(scriptCtx, sectionName, step.Run); err != nil { if firstErr == nil { firstErr = err } + timeoutStage = sectionName break } } + r.warnStageTimeout(jobCtx, scriptCtx, timeoutStage) return r.classifyScriptContextError(jobCtx, scriptCtx, firstErr) } @@ -277,6 +281,7 @@ func (r *Runner) runAfterScriptSteps(jobCtx context.Context, steps []stages.Step return nil }) + r.warnStageTimeout(jobCtx, afterCtx, "after_script") return err } @@ -386,6 +391,21 @@ func (r *Runner) logWarningf(format string, args ...interface{}) { fmt.Fprintf(r.env.Stderr, " %s\033[0m\n", msg) } +// warnStageTimeout fires only on a stage-only deadline; the job-level +// case is owned by the outer runner. +func (r *Runner) warnStageTimeout(jobCtx, stageCtx context.Context, stageName string) { + if !errors.Is(stageCtx.Err(), context.DeadlineExceeded) { + return + } + if errors.Is(jobCtx.Err(), context.DeadlineExceeded) { + return + } + r.logWarningf("%s could not run to completion because the timeout was exceeded. "+ + "For more control over job and script timeouts see: "+ + "https://docs.gitlab.com/ci/runners/configure_runners/#set-script-and-after_script-timeouts", + stageName) +} + // setupGitlabEnv creates the GITLAB_ENV file so user scripts can append // KEY=VALUE lines to define dynamic variables for subsequent steps. func (r *Runner) setupGitlabEnv() error {