npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@tora-dev/dynamodb-lock-client

v0.7.12

Published

A general purpose distributed locking library built for AWS DynamoDB.

Downloads

1,203

Readme

dynamodb-lock-client

Stability: 1 - Experimental

NPM version

A general purpose distributed locking library with fencing tokens built for AWS DynamoDB.

Contributors

@tristanls, @Jacob-Lynch, @simlu, Lukas Siemon, @tomyam1, @deathgrindfreak, @jepetko, @fpronto

Contents

Installation

npm install dynamodb-lock-client

Usage

To run the below example, run:

npm run readme
"use strict";

const AWS = require("aws-sdk");
const DynamoDBLockClient = require("../index.js");

const dynamodb = new AWS.DynamoDB.DocumentClient(
    {
        region: "us-east-1"
    }
);

// "fail closed": if process crashes and lock is not released, lock will
//                never be released (requires human intervention)
const failClosedClient = new DynamoDBLockClient.FailClosed(
    {
        dynamodb,
        lockTable: "my-lock-table-name",
        partitionKey: "mylocks",
        acquirePeriodMs: 1e4
    }
);

failClosedClient.acquireLock("my-fail-closed-lock", (error, lock) =>
    {
        if (error)
        {
            return console.error(error)
        }
        console.log("acquired fail closed lock");
        // do stuff
        lock.release(error => error ? console.error(error) : console.log("released fail closed lock"));
    }
);

// "fail open": if process crashes and lock is not released, lock will
//              eventually expire after leaseDurationMs from last heartbeat
//              sent
const failOpenClient = new DynamoDBLockClient.FailOpen(
    {
        dynamodb,
        lockTable: "my-lock-table-name",
        partitionKey: "mylocks",
        heartbeatPeriodMs: 3e3,
        leaseDurationMs: 1e4
    }
);

failOpenClient.acquireLock("my-fail-open-lock", (error, lock) =>
    {
        if (error)
        {
            return console.error(error)
        }
        console.log(`acquired fail open lock with fencing token ${lock.fencingToken}`);
        lock.on("error", error => console.error("failed to heartbeat!"));
        // do stuff
        lock.release(error => error ? console.error(error) : console.log("released fail open lock"));
    }
);

Tests

At this time, test are implemented for FailOpen lock acquisition and release.

    npm test

Documentation

Setting up the lock table in DynamoDB

Recommended

The DynamoDB lock table needs to be created independently. The following is an example CloudFormation template that would create such a lock table:

AWSTemplateFormatVersion: "2010-09-09"

Resources:

  DistributedLocksStore:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      TableName: "distributed-locks-store"
      BillingMode: PAY_PER_REQUEST

Outputs:

  DistributedLocksStore:
    Value: !GetAtt DistributedLocksStore.Arn

The template above would make your config.partitionKey == "id" and your config.lockTable == "distributed-locks-store".

You can choose to call your config.partitionKey any valid string except fencingToken, leaseDurationMs, lockAcquiredTimeUnixMs, owner, or guid (these attribute names are reserved for use by DynamoDBLockClient library). Your config.partitionKey has to correspond to the partition key (HASH) of the Primary Key of your DynamoDB table.

Using sort key

In some cases, you may be constrained to use a DynamoDB table that requires to specify a sort key. The following is an example CloudFormation template that would create such a lock table:

AWSTemplateFormatVersion: "2010-09-09"

Resources:

  DistributedLocksStore:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
        - AttributeName: sortID
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
        - AttributeName: sortID
          KeyType: RANGE
      TableName: "distributed-locks-store"
      BillingMode: PAY_PER_REQUEST

Outputs:

  DistributedLocksStore:
    Value: !GetAtt DistributedLocksStore.Arn

The template above would make your config.partitionKey == "id", config.sortKey = "sortID", and your config.lockTable == "distributed-locks-store".

You can choose to call your config.partitionKey and config.sortKey any valid string except fencingToken, leaseDurationMs, lockAcquiredTimeUnixMs, owner, or guid (these attribute names are reserved for use by DynamoDBLockClient library). Your config.partitionKey has to correspond to the partition key (HASH) of the Primary Key of your DynamoDB table. Your config.sortKey has to correspond to the sort key (RANGE) of the Primary Key of your DynamoDB table.

DynamoDBLockClient

Public API

new DynamoDBLockClient.FailClosed(config)

  • config: Object
    • dynamodb: AWS.DynamoDB.DocumentClient Instance of AWS DynamoDB DocumentClient.
    • lockTable: String Name of lock table to use.
    • partitionKey: String Name of table partition key (hash key) to use.
    • sortKey: String (Default: undefined) Optional name of table sort key (range key) to use. If specified, all lock ids will be required to contain a sortKey.
    • acquirePeriodMs: Number How long to wait for the lock before giving up. Whatever operation this lock is protecting should take less time than acquirePeriodMs.
    • owner: String Customize owner name for lock (optional).
    • retryCount: Number (Default: 1) Number of times to retry lock acquisition after initial failure. No retries will occur if set to 0.
  • Return: Object Fail closed client.

Creates a "fail closed" client that acquires "fail closed" locks. If process crashes and lock is not released, lock will never be released. This means that some sort of intervention will be required to put the system back into operational state if lock is held and a process crashes while holding the lock.

new DynamoDBLockClient.FailOpen(config)

  • config: Object
    • dynamodb: AWS.DynamoDB.DocumentClient Instance of AWS DynamoDB DocumentClient.
    • lockTable: String Name of lock table to use.
    • partitionKey: String Name of table partition key (hash key) to use.
    • sortKey: String (Default: undefined) Optional name of table sort key (range key) to use. If specified, all lock ids will be required to contain a sortKey.
    • heartbeatPeriodMs: Number (Default: undefined) Optional period at which to send heartbeats in order to keep the lock locked. Providing this option will cause heartbeats to be sent.
    • leaseDurationMs: Number The length of lock lease duration. If the lock is not renewed via a heartbeat within leaseDurationMs it will be automatically released.
    • owner: String Customize owner name for lock (optional).
    • retryCount: Number (Default: 1) Number of times to retry lock acquisition after initial failure. No retries will occur if set to 0.
    • trustLocalTime: Boolean (Default: false) If set to true, when the client retrieves an existing lock, it will use local time to determine if leaseDurationMs has elapsed (and shorten its wait time accordingly) instead of always waiting the full leaseDurationMs milliseconds before making an acquisition attempt.
  • Return: Object Fail open client.

Creates a "fail open" client that acquires "fail open" locks. If process crashes and lock is not released, lock will eventually expire after leaseDurationMs from last heartbeat sent (if any). This means that if process acquires a lock, goes to sleep for more than leaseDurationMs, and then wakes up assuming it still has a lock, then it can perform an operation ignoring other processes that may assume they have a lock on the operation.

client.acquireLock(id, callback)

  • id: String|Buffer|Number|Object Unique identifier for the lock. If the type of id is String|Buffer|Number the type must correspond to lock table's partition key type. If the type of id is Object, it is expected to have the following format:
    {
      [config.partitionKey]: String|Buffer|Number,
      [config.sortKey]: String|Buffer|Number
    }
    For example, if config.partitionKey = "myPartitionKey" and config.sortKey = "mySortKey" and partition key value is id1234 and sort key value is abcd, then the Object would be:
    {
      myPartitionKey: "id1234",
      mySortKey: "abcd"
    }
    Sort key part of id is only required if lock is configured with a sort key. The types of partition key and sort key must correspond to lock table's partition key and sort key types.
  • callback: Function (error, lock) => {}
    • error: Error Error, if any.
    • lock: DynamoDBLockClient.Lock Successfully acquired lock object. Lock object is an instance of EventEmitter. If the lock is acquired via a fail open client configured to heartbeat, then the returned lock may emit an error event if a heartbeat operation fails.
      • fencingToken: Integer fail open locks only Integer monotonically incremented with every "fail open" lock acquisition to be used for fencing. Heartbeats do not increment fencingToken.

Attempts to acquire a lock. If lock acquisition fails, callback will be called with an error and lock will be falsy. If lock acquisition succeeds, callback will be called with lock, and error will be falsy.

Fail closed client will attempt to acquire a lock. On failure, client will retry after acquirePeriodMs up to retryCount times. After retryCount failures, client will fail lock acquisition. On successful acquisition, lock will be locked until lock.release() is called successfuly.

Fail open client will attempt to acquire a lock. On failure, if trustLocalTime is false (the default), client will retry after leaseDurationMs. If trustLocalTime is true, the client will retry after Math.max(0, leaseDurationMs - (localTimeMs - lockAcquiredTimeMs)) where localTimeMs is "now" and lockAcquiredTimeMs is the lock acquisition time recorded in the retrieved lock. Lock acquisition will be retried up to retryCount times. After retryCount failures, client will fail lock acquisition. On successful acquisition, if heartbeatPeriodMs option is not specified (heartbeats off), lock will expire after leaseDurartionMs. If heartbeatPeriodMs option is specified, lock will be renewed at heartbeatPeriodMs intervals until lock.release() is called successfuly. Additionally, if heartbeatPeriodMs option is specified, lock may emit an error event if it fails a heartbeat operation.

lock.release(callback)

  • callback: Function error => {}
    • error: Error Error, if any. No error implies successful lock release.

Releases previously acquired lock.

Fail closed lock is deleted, so that it can be acquired again.

Fail open lock heartbeats stop, and its leaseDurationMs is set to 1 millisecond so that it expires "immediately". The datastructure is left in the datastore in order to provide continuity of fencingToken monotonicity guarantee.

Releases

We follow semantic versioning policy (see: semver.org):

Given a version number MAJOR.MINOR.PATCH, increment the:

MAJOR version when you make incompatible API changes, MINOR version when you add functionality in a backwards-compatible manner, and PATCH version when you make backwards-compatible bug fixes.

caveat: Major version zero is a special case indicating development version that may make incompatible API changes without incrementing MAJOR version.