A GitHub Action for serializing workflow runs
GitHub Actions is an event-oriented system. Your workflows run in response to events and are triggered independently and without coordination. In a shared repository, if two or more people merge pull requests, each will trigger workflows without regard to one another.
This can be problematic for workflows used as part of a continuous deployment process. You might want to let an in-flight deployment complete before progressing further with the next workflow. This is the usecase turnstyle action targets.
The typical setup for turnstyle involves adding job step using softprops/turnstyle@v3.
The examples below use the moving v3 major-version tag. If you need a
change that has merged to master but has not been released yet, pin the
merged commit SHA until the next v3 release is published.
name: Main
on: push
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
+ - name: Turnstyle
+ uses: softprops/turnstyle@v3
- name: Deploy
run: sleep 30To avoid waiting prolonged periods of time, you may wish to bail on a run or continuing a workflow run regardless of the status of the previous run.
You can bail from a run using the built-in GitHub Actions jobs.<job_id>.timeout-minutes setting
name: Main
on: push
jobs:
main:
+ timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Turnstyle
uses: softprops/turnstyle@v3
- name: Deploy
run: sleep 30You can also limit how long you're willing to wait before moving on with jobs.<job_id>.steps.with.continue-after-seconds
name: Main
on: push
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Turnstyle
uses: softprops/turnstyle@v3
with:
+ continue-after-seconds: 180
- name: Deploy
run: sleep 30or before aborting the step with jobs.<job_id>.steps.with.abort-after-seconds
name: Main
on: push
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Turnstyle
uses: softprops/turnstyle@v3
with:
+ abort-after-seconds: 180
- name: Deploy
run: sleep 30you can also wait on a specific job (and step) to complete in a run by using the jobs.<job_id>.steps.with.job-to-wait-for
and jobs.<job_id>.steps.with.step-to-wait-for inputs. Specify the name of the job/step to wait for.
name: Main
on: push
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Turnstyle
uses: softprops/turnstyle@v3
with:
+ job-to-wait-for: "main"
+ step-to-wait-for: "Deploy"
- name: Deploy
run: sleep 30Finally, you can use the force_continued output to skip only a subset of steps
by setting continue-after-seconds and conditioning future steps with
if: ! steps.<step id>.outputs.force_continued
name: Main
on: push
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Turnstyle
id: turnstyle
uses: softprops/turnstyle@v3
with:
+ continue-after-seconds: 180
- name: Deploy
+ if: ! steps.turnstyle.outputs.force_continued
run: sleep 30By default, Turnstyle waits on runs from the current branch. To wait on another
branch, set branch.
name: Main
on: pull_request
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Turnstyle
uses: softprops/turnstyle@v3
with:
branch: master
- name: Deploy
run: sleep 30You can use previous_run_id to download artifacts from the run Turnstyle
waited on.
name: Main
on: push
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Turnstyle
id: turnstyle
uses: softprops/turnstyle@v3
- name: Download previous artifact
if: steps.turnstyle.outputs.previous_run_id != ''
uses: actions/download-artifact@v4
with:
name: build
github-token: ${{ github.token }}
run-id: ${{ steps.turnstyle.outputs.previous_run_id }}Turnstyle can run from a called reusable workflow. If the same caller workflow invokes the reusable workflow repeatedly, the default queue is scoped to that caller workflow.
If several caller workflows should share one queue through the same reusable
workflow, set queue-name to a stable value and include that value in each
caller workflow's run name or workflow name. Turnstyle uses that queue name to
find matching active runs across workflows.
Caller workflow:
name: Deploy API
run-name: deploy-queue ${{ github.ref_name }} ${{ github.run_number }}
on: push
jobs:
deploy:
uses: ./.github/workflows/reusable-deploy.ymlCalled reusable workflow:
name: Reusable deploy
on:
workflow_call:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Turnstyle
uses: softprops/turnstyle@v3
with:
queue-name: deploy-queue
- name: Deploy
run: sleep 30| Name | Type | Description |
|---|---|---|
token |
string | GitHub access token used for Actions API reads (defaults to github.token) |
continue-after-seconds |
number | Maximum number of seconds to wait before moving forward (unbound by default). Mutually exclusive with abort-after-seconds |
abort-after-seconds |
number | Maximum number of seconds to wait before aborting the job (unbound by default). Mutually exclusive with continue-after-seconds |
poll-interval-seconds |
number | Number of seconds to wait in between checks for previous run completion (defaults to 60) |
same-branch-only |
boolean | Only wait on other runs from the same branch (defaults to true) |
branch |
string | Branch name to use for same-branch filtering (defaults to the current branch) |
initial-wait-seconds |
number | Total elapsed seconds within which period the action will refresh the list of current runs, if no runs were found in the first attempt |
job-to-wait-for |
string | Name of the workflow's job to wait for (unbound by default). |
step-to-wait-for |
string | Name of the step to wait for (unbound by default). Requires job-to-wait-for to be set. |
queue-name |
string | Custom substring used to group matching runs across workflows (defaults to the current workflow only). |
| Name | Type | Description |
|---|---|---|
force_continued |
boolean | True if continue-after-seconds is used and the step using turnstyle continued. False otherwise. |
previous_run_id |
string | The ID of the previous workflow run that Turnstyle waited on, if one was found. |
previous_run_url |
string | The URL of the previous workflow run that Turnstyle waited on, if one was found. |
Because this application leverages the GITHUB_TOKEN to make API requests, the
permissions granted to the token must be sufficient to make the API requests.
By default, the token has wide enough permissions to allow all API requests
made by this action. If you are customizing your token permissions, you must
explicitly specify all permissions, including those that you need that would
otherwise be granted by the defaults. See "Permissions for the
GITHUB_TOKEN"
In the GitHub Actions documentation.
If you need to specify explicit permissions for the API requests made by this action, the permissions required are:
actions:read- this permission is required for the workflow, run, job, and step read requests made by this action.
At this time there is no way to coordinate between workflow runs beyond waiting. For those using private repositories, you are charged based on the time your workflow spends running. Waiting within one workflow run for another to complete will incur the cost of the time spent waiting.
Doug Tangren (softprops) 2020