Skip to main content

Mutex

An advisory lock service for CI/CD workflows, implemented as a GitHub Action. It helps prevent race conditions and ensures that critical sections of your pipeline are executed by only one job at a time.

GitHub Repository GitHub Stars GitHub Forks

How it works

This action uses a PostgreSQL database to manage locks. When a workflow job needs to acquire a lock, it communicates with the lock service. If the lock is available, it's granted, and the job proceeds. If not, the job can wait or fail, depending on your workflow configuration.

Features

  • Advisory Locking: Create and manage locks within your GitHub Actions workflows.
  • Pull Request Integration: Lock and release events are posted as PR comments.
  • Slack Notifications: Choose if you want to be notified in your Slack channels about locking events.
  • Easy Disabling: Skip locking for specific pull requests by:
    • adding a SKIP_MUTEX label
    • including SKIP_MUTEX in the PR's description or comment
    • or defining SKIP_MUTEX=1 as an environment variable.

Usage Example

Here is an example of how to use the mutex action in a workflow:

- name: Acquire Lock
uses: releasetools/mutex@v1
permissions:
contents: read
pull-requests: write
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
command: "lock"
id: "my-resource"
slack-channel: "#ci-cd"

Any other workflows or actions using a mutex on the same lock id, will not run until the lock is released.

Configuration

Prerequisites

  • PostgreSQL database: This action requires access to a PostgreSQL database to store lock information. You can use any standard Postgres provider. If you need a free one for getting started, consider using Neon.

Environment Variables

The action supports the following environment variables (env:).

DATABASE_URL

Connection string for a PostgreSQL database. The action will create a table named releasetools_mutex if it doesn't exist. If the role specified in the connection string cannot create tables, ensure such a table exists. You can find the schema definition in database.ts.

GITHUB_TOKEN

The action needs access to the GitHub API. It can be passed via ${{ secrets.GITHUB_TOKEN }}. The workflow needs additional permissions:

permissions:
contents: read
pull-requests: write

SLACK_BOT_TOKEN

The Slack Bot Token for sending notifications. It requires the chat:write permission, and the associated bot must be invited to the specified slack-channel, otherwise it will fail to post.

Action Inputs

The action can be configured using inputs (with:).

command

Required. The command to execute (e.g., lock or release).

id

Required. A unique identifier for the lock.

reason

Optional reason for taking the lock. Useful to provide context regarding which service took the lock and why.

expiration

Lock expiration in seconds from current time. Defaults to 60 seconds in the future.

max-wait

Maximum time in seconds to wait to acquire the lock, before failing. If not specified, it defaults to -1 which results in using the specified expiration as a timeout for the current run.

poll-interval

Allows changing the polling interval. Useful for long-duration locks.

auto-release

Used to signal if a lock should be automatically released when the workflow job ends. Defaults to true.

disable-pr-updates

By default, a comment will be posted on the Pull Request running the action, when locks are acquired or released. Set it to true to never post comments on PRs.

slack-channel

Required for Slack notifications. The Slack channel to post updates to (e.g., C12345678). The bot that owns the SLACK_BOT_TOKEN should be a member of this channel.

See Slack API docs for channel ID formats.

Advanced Usage

Multiple Locks

You can use multiple locks in the same workflow:

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Acquire Database Lock
uses: releasetools/mutex@v1
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
command: "lock"
id: "database-migration"
reason: "Running database migrations"

- name: Acquire Deployment Lock
uses: releasetools/mutex@v1
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
command: "lock"
id: "production-deployment"
reason: "Deploying to production"

# Your deployment steps here

- name: Release Deployment Lock
uses: releasetools/mutex@v1
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
command: "release"
id: "production-deployment"

- name: Release Database Lock
uses: releasetools/mutex@v1
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
command: "release"
id: "database-migration"

Conditional Locking

Skip locking based on conditions:

- name: Acquire Lock
if: ${{ !contains(github.event.pull_request.labels.*.name, 'SKIP_MUTEX') }}
uses: releasetools/mutex@v1
# ... configuration

Development

All contributions are welcome!

  1. Clone the repository:

    git clone https://github.com/releasetools/mutex.git
    cd mutex
  2. Install dependencies and pre-commit hooks:

    npm install
    npm run prepare

The main entry point is main.ts, which handles 'lock' or 'release' actions. A post-job script in post.ts handles automatic lock release if enabled.

You can learn about creating GitHub actions in this tutorial.

Releasing

You can use the releasetools cli to create release tags.

Run this command to tag the HEAD commit and also update the v1 tag.

rt git::release --major --sign --force --push v1.0.2

Since mutex is a Javascript-based action, no other step is needed to make a new release available.

Troubleshooting

Common Issues

Database Connection Errors

  • Ensure your DATABASE_URL is correct and the database is accessible
  • Verify that the database user has sufficient permissions to create tables
  • Check that the PostgreSQL server is running and accepting connections

Permission Errors

  • Make sure your workflow has the required permissions:
    permissions:
    contents: read
    pull-requests: write

Slack Integration Issues

  • Verify that the SLACK_BOT_TOKEN has chat:write permissions
  • Ensure the bot is added to the specified channel
  • Check that the channel ID format is correct

Getting Help

If you encounter issues:

  1. Check the GitHub Issues for similar problems
  2. Review the action logs in your GitHub workflow
  3. Verify your configuration against the examples above
  4. Open a new issue with detailed information about your setup

License

Copyright © 2024 ReleaseTools

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.