Giter VIP home page Giter VIP logo

amazon-dynamodb-lock-client's Introduction

Amazon DynamoDB Lock Client

The Amazon DynamoDB Lock Client is a general purpose distributed locking library built for DynamoDB. The DynamoDB Lock Client supports both fine-grained and coarse-grained locking as the lock keys can be any arbitrary string, up to a certain length. DynamoDB Lock Client is an open-source project that will be supported by the community. Please create issues in the GitHub repository with questions.

Build Status

Use cases

A common use case for this lock client is: let's say you have a distributed system that needs to periodically do work on a given campaign (or a given customer, or any other object) and you want to make sure that two boxes don't work on the same campaign/customer at the same time. An easy way to fix this is to write a system that takes a lock on a customer, but fine-grained locking is a tough problem. This library attempts to simplify this locking problem on top of DynamoDB.

Another use case is leader election. If you only want one host to be the leader, then this lock client is a great way to pick one. When the leader fails, it will fail over to another host within a customizable leaseDuration that you set.

Getting Started

To use the Amazon DynamoDB Lock Client, declare a dependency on the latest version of this artifact in Maven in your pom.xml.

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>dynamodb-lock-client</artifactId>
    <version>1.2.0</version>
</dependency>

Then, you need to set up a DynamoDB table that has a hash key on a key with the name key. For your convenience, there is a static method in the AmazonDynamoDBLockClient class called createLockTableInDynamoDB that you can use to set up your table, but it is also possible to set up the table in the AWS Console. The table should be created in advance, since it takes a couple minutes for DynamoDB to provision your table for you. The AmazonDynamoDBLockClient has JavaDoc comments that fully explain how the library works. Here is some example code to get you started:

import java.io.IOException;
import java.net.URI;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

public class LockClientExample {
    @Test
    public void usageExample() throws InterruptedException, IOException {
        // Inject client configuration to the builder like the endpoint and signing region
        final DynamoDbClient dynamoDB = DynamoDbClient.builder()
                .region(Region.US_WEST_2).endpointOverride(URI.create("http://localhost:4567"))
                .build();
        // Whether or not to create a heartbeating background thread
        final boolean createHeartbeatBackgroundThread = true;
        //build the lock client
        final AmazonDynamoDBLockClient client = new AmazonDynamoDBLockClient(
            AmazonDynamoDBLockClientOptions.builder(dynamoDB, "lockTable")
                    .withTimeUnit(TimeUnit.SECONDS)
                    .withLeaseDuration(10L)
                    .withHeartbeatPeriod(3L)
                    .withCreateHeartbeatBackgroundThread(createHeartbeatBackgroundThread)
                    .build());
        //try to acquire a lock on the partition key "Moe"
        final Optional<LockItem> lockItem =
                client.tryAcquireLock(AcquireLockOptions.builder("Moe").build());
        if (lockItem.isPresent()) {
            System.out.println("Acquired lock! If I die, my lock will expire in 10 seconds.");
            System.out.println("Otherwise, I will hold it until I stop heartbeating.");
            client.releaseLock(lockItem.get());
        } else {
            System.out.println("Failed to acquire lock!");
        }
        client.close();
    }
}

Permissions

The following permissions are required in order to use the AmazonDynamoDBLockClient to acquire or release a lock:

  • dynamodb:DeleteItem
  • dynamodb:GetItem
  • dynamodb:PutItem
  • dynamodb:Scan
  • dynamodb:UpdateItem

If you also create a table using AmazonDynamoDBLockClient.createLockTableInDynamoDB, you need these permissions:

  • dynamodb:CreateTable

If you want to ensure a table exists using AmazonDynamoDBLockClient.assertLockTableExists or AmazonDynamoDBLockClient.lockTableExists, the following permissions are necessary as well:

  • dynamodb:DescribeTable

Selected Features

Send Automatic Heartbeats

When you call the constructor AmazonDynamoDBLockClient, you can specify createHeartbeatBackgroundThread=true like in the above example, and it will spawn a background thread that continually updates the record version number on your locks to prevent them from expiring (it does this by calling the sendHeartbeat() method in the lock client.) This will ensure that as long as your JVM is running, your locks will not expire until you call releaseLock() or lockItem.close()

Acquire lock with timeout

You can acquire a lock via two different methods: acquireLock or tryAcquireLock. The difference between the two methods is that tryAcquireLock will return Optional.absent() if the lock was not acquired, whereas acquireLock will throw a LockNotGrantedException. Both methods provide optional parameters where you can specify an additional timeout for acquiring the lock. Then they will try to acquire the lock for that amount of time before giving up. They do this by continually polling DynamoDB according to an interval you set up. Remember that acquireLock/tryAcquireLock will always poll DynamoDB for at least the leaseDuration period before giving up, because this is the only way it will be able to expire stale locks.

This example will poll DynamoDB every second for 5 additional seconds (beyond the lease duration period), trying to acquire a lock:

LockItem lock = lockClient.acquireLock("Moe", "Test Data", 1, 5, TimeUnit.SECONDS);

Acquire lock without blocking the user thread.

Example Use Case: Suppose you have many messages that need to be processed for multiple lockable entities by a limited set of processor-consumers. Further suppose that the processing time for each message is significant (for example, 15 minutes). You also need to prevent multiple processing for the same resource.

  @Test
    public void acquireLockNonBlocking() throws LockAlreadyOwnedException {
        AcquireLockOptions lockOptions = AcquireLockOptions.builder("partitionKey")
                                        .withShouldSkipBlockingWait(false).build();
        LockItem lock = lockClient.acquireLock(lockOptions);
    }

The above implementation of the locking client, would try to acquire lock, waiting for at least the lease duration (15 minutes in our case). If the lock is already being held by other worker. This essentially blocks the threads from being used to process other messages in the queue.

So we introduced an optional behavior which offers a Non-Blocking acquire lock implementation. While trying to acquire lock, the client can now optionally set shouldSkipBlockingWait = true to prevent the user thread from being blocked until the lease duration, if the lock has already been held by another worker and has not been released yet. The caller can chose to immediately retry the lock acquisition or to back off and retry the lock acquisition, if lock is currently unavailable.

    @Test
    public void acquireLockNonBlocking() throws LockAlreadyOwnedException {
        AcquireLockOptions lockOptions = AcquireLockOptions.builder("partitionKey")
                                        .withShouldSkipBlockingWait(true).build();
        LockItem lock = lockClient.acquireLock(lockOptions);
    }

If the lock does not exist or if the lock has been acquired by the other machine and is stale (has passed the lease duration), this would successfully acquire the lock.

If the lock has already been held by another worker and has not been released yet and the lease duration has not expired since the lock was last updated by the current owner, this will throw a LockCurrentlyUnavailableException exception. The caller can chose to immediately retry the lock acquisition or to delay the processing for that lock item by NACKing the message.

Read the data in a lock without acquiring it

You can read the data in the lock without acquiring it, and find out who owns the lock. Here's how:

LockItem lock = lockClient.getLock("Moe");

How we handle clock skew

The lock client never stores absolute times in DynamoDB -- only the relative "lease duration" time is stored in DynamoDB. The way locks are expired is that a call to acquireLock reads in the current lock, checks the RecordVersionNumber of the lock (which is a GUID) and starts a timer. If the lock still has the same GUID after the lease duration time has passed, the client will determine that the lock is stale and expire it. What this means is that, even if two different machines disagree about what time it is, they will still avoid clobbering each other's locks.

Testing the DynamoDB Locking client

To run all integration tests for the DynamoDB Lock client, issue the following Maven command:

mvn clean install -Pintegration-tests

External release to Sonatype

mvn deploy -Possrh-release

amazon-dynamodb-lock-client's People

Contributors

amcp avatar dependabot[bot] avatar hyandell avatar justinwlin-amazon avatar khanshariquem avatar lcabancla avatar shetsa-amzn avatar zoeji avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

amazon-dynamodb-lock-client's Issues

Interest in a TypeScript port?

Would the team be interested in the contribution of a DynamoDB Lock Client for TypeScript?

If I wrote this could we work on absorbing it into an official package?

Context

I worked on and helped build the DynamoDB Lock Client, working with Sasha Slutsker and David Yanacek, while I was employed at Amazon as a former Principal Engineer (2007-2021) [1], and I'm currently at a company that use TypeScript, finding myself desiring this library's capabilities.

I believe I'm acquainted with its design and design decisions after the passage of time sufficiently that I could build a TypeScript port with identical or nearly identical semantics, potentially even compatible with the existing library (though that wouldn't be a primary goal – given that it's rare in well-designed systems, IMO) for multiple apps to access a data store).

Proposal

I'd probably propose creating a separate package for the TypeScript variant: would amazon-dynamodb-lock-client-ts work? This would facilitate keep their source histories different (an advantage if we're making no major architectural or compatibility changes, which seems unlikely) and also publishing keep the TS package minimal so as to publish to NPM.

Interoperability: Nice to have

Ideally the two libraries would be able to interoperate with locking data in the same table, but that's a nice-to-have not a hard requirement in my opinion, since I don't have a use-case for two different programs interacting using locks with the same DDB table. That would be risking subtle bugs and problems.

Additionally, as general design principle I strive to have only a single program or service interact with each data store, like DynamoDB table, so I don't expect to come across a use case where both TS and Java programs are locking the same table.

So I'd aim for compatibility in naming and structure, but (depending on time available) not necessarily test or prove that they can actually interoperate; instead they'll just leave recognizable patterns in the table items, and use similar techniques for locking, heartbeating, and updates.

[1] My name is not in the package's commit history or developer list because it began as an internal package, and we basically started over for the public OSS version, after having made that decision; and I was not involved in the OSS work.

Service: AmazonDynamoDBv2; Status Code: 400; Error Code: AccessDeniedException

Hi all
could you please help
I use dynamodb-lock-client v 1.1.0
and I spot an issue that AmazonDynamoDBLockClient does not have an access to dynamodb table.
I was trying to use following methods tryGetOrAcquireLock and createLockTableInDynamoDB, and both returned an error Service: AmazonDynamoDBv2; Status Code: 400; Error Code: AccessDeniedException.

However the the client AmazonDynamoDB that I injected has access.

Thanks.

Check for expired lock on first iteration rather than waiting for LEASE_DURATION milliseconds

Apologies if I'm not understanding this properly, but the behaviour I'm seeing is this:

  1. A lock is put in place and never gets released but it does expire after the lease period ends
  2. If another client attempts to acquire this lock, the acquireLock(...) method on its first loop iteration always sleeps for the lease duration, rather than doing an upfront check to determine that the lock had already expired

Is this intentional? In my case it's failing to work with another library (spring-integration-aws) which expects a result from the acquireLock() method within 10 seconds (when my lease duration is set to 20 seconds).

Detect stale locks by heartbeat period rather than lease duration

We are looking for a way to solve the problem of lambdas being invoked multiple times for the same event. We have some long-running lambdas (processing uploaded files up to 10GB in size), and sometimes we get the Lambda triggered a 2nd (or 3rd) time while the first invocation is still running normally.

I thought we could use this library to acquire a lock in the lambda, but if the lease duration approaches the runtime limit of a lambda (currently 15 minutes), there will never be enough time after detecting a stale lock for the next client to do its work. For example:

  1. We have some files that take 12-15 minutes to process, so we want to set our maximum lease time to 15 minutes.
  2. If a lambda invocation crashed without releasing the lock, another lambda would wait the lease duration (15 minutes) to determine that the lock was stale. Thus, the second lambda will use its entire invocation time limit just waiting to acquire the lock.

Would there be any drawback to using the heartbeat period (plus some configurable buffer) to detect stale locks instead of the lease duration? This would require the heartbeatPeriod being added to the item stored. But having done that, the How we handle clock skew section could become:

...a call to acquireLock reads in the current lock, checks the RecordVersionNumber of the lock (which is a GUID) and starts a timer. If the lock still has the same GUID after the lease duration time heartbeat period (plus a buffer) has passed, the client will determine that the lock is stale and expire it.

With this approach, I could set my lease duration to 15min, and my heartbeat period to something like 5s (plus 5s buffer). Then I could detect a stale lock within ~10s rather than 15 minutes.

Suggestion: Add a [noWaitTimeForLock] flag for AcquireLockOptions

It would be useful to have to possibly to try to acquire a lock without having to wait a full LEASE_DURATION.

https://github.com/awslabs/dynamodb-lock-client/blob/e44962dcb08d51f134a42c9e36fbd424cfb781c2/src/main/java/com/amazonaws/services/dynamodbv2/AmazonDynamoDBLockClient.java#L443

Currently we are bypassing this behaviour by adding a negative time for withAdditionalTimeToWaitForLock. This is not so clean, that's why having a boolean flag noWaitTimeForLock for AcquireLockOptions that subtract the lease_duration from millisecondsToWait would be a nice feature.

Ability to re-use existing column key and existing table for locking table

Problem Statement

From the Documentation i found that in-order to acquire lock i need to have a table whose partition key is named as "key".
"To use the AmazonDynamoDBLockClient, declare a dependency on the latest version of AmazonDynamoDBLockClient and merge it into your version set. Then, you need to set up a DynamoDB table that has a hash key on a key with the name "key.""

Feature Request

It would be great if I can re-use my existing table say
TableName: Sample
Partition Key: workspace_id
column_2: status
column_3: workflow_type

I would like to acquire lock to the key "workspace_id" on the table "sample"
so that I dont have to juggle between two tables to find which is locked and which is not.

Appreciate your support.

Regards,
Dheepthi

DynamoDB lock client additionalAttributes uses

what is the use of additionalAttributes used during ddb lock acquireLock method call. Is it used along with key to find the lock or its just extra data that we can store and then can get in getLock() call ?

My use case
first call -> key k1 and version v2 -> acquireLock(k1).withAdditionalAttributes(v2)
second call -> key k1 and version v1 -> getLock(k1) -> check additional Attributes data -> v1 is older then v2 so reject current call

can client.acquireLock callback be made asynchronous?

Will there be undesirable effects from doing something this --> client.acquireLock(id, async (err, lock) => {})

A lot of my code that has to happen between setting the lock and releasing is using async/await. Would setting this callback as asynchronous cause issues?

Closing the client logs an exception

AmazonDynamoDBLockClient:955 logger.info("Heartbeat thread recieved interrupt, exiting run() (possibly exiting thread)", e);

Prints
[dynamodb-lock-client-1] INFO com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient - Heartbeat thread recieved interrupt, exiting run() (possibly exiting thread)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient.run(AmazonDynamoDBLockClient.java:955)
at java.lang.Thread.run(Thread.java:748)

Which kind of looks like an error occurred.

In my opinion it should
a) Log as debug
b) Not log the stack trace

Is this project dead?

I wanted to ask whether this project is dead because I would need #33 to be resolved before I can use this library. If yes, are there any alternatives I can use?

Batch Locking

Provide ability to acquire lock on multiple keys in a single dynamo db query (BatchPutItem or BatchWriteItem)
Currently, the implementation of acquireLock is such that it can only acquire lock for a single key at a time, using PutItem
We have a use case where we need to iterate over a list of objects and acquire a lock on each before proceeding further. When this list hits a considerable size, having multiple dynamodb queries increases the latency manifolds. We were looking on acquiring all the locks in a single query and release them in a single query as well to avoid multiple dynamodb queries and thereby improving latency.

Documentation on inter-region locking

Does this lock client supports inter-region locking? documentation should clarify whether this library addresses inter-region locking & solution patterns around that.

Support querying all locks with the same partition key

Support querying all locks with the same partition key.

The function signature can be similar to:

    /**
     * <p>
     * Retrieves the locks with partition_key = {@code key}.
     * </p>
     * <p>
     * Not that this may return a lock item even if it was released.
     * </p>
     *
     * @param key the partition key
     * @param deleteOnRelease Whether or not the {@link LockItem} should delete the item
     *                        when {@link LockItem#close()} is called on it.
     * @return A non parallel {@link Stream} of {@link LockItem}s that has the partition key in
     * DynamoDB. Note that the item can exist in the table even if it is
     * released, as noted by {@link LockItem#isReleased()}.
     */
    public Stream<LockItem> getLocksByPartitionKey(String key, final boolean deleteOnRelease) {

Currently the only way to get multiple leases is by scanning the table ie - via getAllLocksFromDynamoDB(). This is useful in cases where we only want to query a subset of the leases. For example, leases can be categorized and each lease in the same category has the same partition key but different sort keys.

Loading all leases by partition key should require an efficient query instead of a full scan.

Python port

Hi,

I have written up a python "port" of this library - would love to get your thoughts on the same.

https://python-dynamodb-lock.readthedocs.io/en/latest/readme.html

While it is heavily "inspired" by this java version, it does have a few differences - as listed out here:

https://python-dynamodb-lock.readthedocs.io/en/latest/usage.html#differences-from-java-implementation

Would like to understand if you see any problems with the deviations, and if you have any suggestions/comments.

regards,
Mohan

Document default permission set

It would be nice to document the minimum permissions needed in order to utilize this library. From what I can tell, consumers need:

  • dynamodb:DeleteItem
  • dynamodb:DescribeTable
  • dynamodb:GetItem
  • dynamodb:PutItem
  • dynamodb:Scan
  • dynamodb:UpdateItem

... with an optional dynamodb:CreateTable, if someone wanted to write their application so that it could create tables in DynamoDB (which should probably be discouraged if this library is being used for a web application, internal or not).

Document limitations of locks issued

The documentation states that e.g if used for leader election the lock can be relied upon to ensure there is only one leader.

As per https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html which is referenced in Amazons own library https://aws.amazon.com/builders-library/leader-election-in-distributed-systems/ this isn’t correct without a fence using a monotonic version number.

This library seems to only use a UUID for the record version meaning clients can’t implement a fence correctly.

Could the documentation either mention thIs limitation or mention how a fence can be implemented by clients if I’ve missed something.

Getting this exception while app initialisation. Is this some known issue?

{"thread":"dynamodb-lock-client-1","level":"INFO","loggerName":"com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient","message":"Heartbeat thread recieved interrupt, exiting run() (possibly exiting thread)","thrown":{"commonElementCount":0,"localizedMessage":"sleep interrupted","message":"sleep interrupted","name":"java.lang.InterruptedException","extendedStackTrace":[{"class":"java.lang.Thread","method":"sleep","file":"Thread.java","line":-2,"exact":false,"location":"?","version":"1.8.0_232"},{"class":"com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient","method":"run","file":"AmazonDynamoDBLockClient.java","line":1184,"exact":true,"location":"dynamodb-lock-client-1.1.0.jar","version":"?"},{"class":"java.lang.Thread","method":"run","file":"Thread.java","line":748,"exact":true,"location":"?","version":"1.8.0_232"}]},"endOfBatch":false,"loggerFqcn":"org.apache.commons.logging.LogAdapter$Log4jLog","instant":{"epochSecond":1635769897,"nanoOfSecond":82000000},"contextMap":{},"threadId":62,"threadPriority":5}

testCallbackNotCalledOnClosingClient() Unit Test Question

Can you explain the logic behind this unit test? When the client attempts to acquire the lock, it registers the session monitor and starts the background thread that is the callback action.

final AcquireLockOptions options = stdSessionMonitorOptions(2 * heartbeatFreq, intSetter);
final LockItem item = heartClient.acquireLock(options);

This effectively ends up calling:

private void tryAddSessionMonitor(final String lockName, final LockItem lock) {
    if (lock.hasSessionMonitor() && lock.hasCallback()) {
        final Thread monitorThread = lockSessionMonitorChecker(lockName, lock);
        monitorThread.setDaemon(true);
        monitorThread.start();
        this.sessionMonitors.put(lockName, monitorThread);
    }
}

So, it starts the callback thread. When the client is closed, it calls removeKillSessionMonitor as it releases each lock, but that just interrupts the thread, so isn't this effectively a race condition between the callback thread and interrupting it? The only other place it looks like you might prevent the callback from being called is during the lockSessionMonitorChecker call where it checks the millisUntilDangerZone > 0, however since there's not necessarily a heartbeat sent yet, that value might be negative and appears to be another race condition to short-circuit the callback from ever being called.

Can you explain the logic behind this unit test and the expected behavior? Thanks!

Release supported version using AWS SDK v2.

This pull request integrated with AWS SDK v2 on the master branch:

#19

However, it is described as "preliminary". From what I can tell, there is not yet a formally supported release with AWS SDK v2 support.

I'd like to request completion of this feature and distribution of a new Maven release. Right now, projects using dynamodb-lock-client are forced into a transitive dependency on AWS SDK v1, even if their codebase otherwise has migrated to AWS SDK v2.

Is it possible to know the reason of heartbeat failure

Hi,

I am writing an application and want to use this client in my leasing algorithm. I understand that I can pass a handler that lock client will call in case heartbeat fails or lock is expired. However this can also happen due to failure to contact dynamodb. Is it possible to find out what happened. I want my application to have different behavior in case dynamodb outage and different in case the lock is actually expired.

Thanks

Local lockItem does not get updated when sendHeartbeat sends new data

When the client holds the lock and sendHeartbeat(options) is called with new data, the dynamodb record gets updated but the local lockItem does not. This is a problem when the local lockItem is retrieved via getLock(). Calling getData() on the lockItem will yield the old data.

See test case below:

    @Test
    public void testSendHeartbeatChangeData() throws IOException, LockNotGrantedException, InterruptedException {

        final String data1 = new String("testSendHeartbeatChangeData1" + SECURE_RANDOM.nextDouble());
        final String data2 = new String("testSendHeartbeatChangeData2" + SECURE_RANDOM.nextDouble());

        final LockItem item = this.lockClient
            .acquireLock(AcquireLockOptions.builder("testKey1").withData(ByteBuffer.wrap(data1.getBytes()))
                    .withDeleteLockOnRelease(true).withReplaceData(true).build());
        assertEquals(data1, new String(item.getData().get().array()));

        assertEquals(data1, byteBufferToString(this.lockClient.getLockFromDynamoDB(
                GetLockOptions.builder("testKey1").build()).get().getData().get()));
        this.lockClient.sendHeartbeat(SendHeartbeatOptions.builder(item).withData(ByteBuffer.wrap(data2.getBytes())).build());
        assertEquals(data2, byteBufferToString(this.lockClient.getLockFromDynamoDB(
                GetLockOptions.builder("testKey1").build()).get().getData().get()));

        // This will fail since the lockItem returned by getLock() is not updated.
        assertEquals(data2, byteBufferToString(this.lockClient.getLock(
            "testKey1", Optional.empty()).get().getData().get()));
        item.close();
    }

LockCurrentlyNotAvailable with shouldSkipBlockingWait

Greetings gentlemen,

we are facing problem with acquiring lock with 'shouldSkipBlockingWait' set to true;
Find reproducible test bellow.
To reproduce this manually, just kill process while owning lock, start another one and try to acquire it with options as in junit...
Is this bug or just bad usage ?

Version being used is 1.1.0
Thanks

  @Test
  void notExpectedBehaviour() throws Exception{

    final long leaseDuration = 1_000;
    final long heartBeatPeriod = 200;
    final String partition = "super_key_LOCK";

    AcquireLockOptions lockOptions = AcquireLockOptions
        .builder(partition)
        .withAcquireReleasedLocksConsistently(true)
        .withShouldSkipBlockingWait(true)
        .build();

    AmazonDynamoDBLockClient lockClientOne = new AmazonDynamoDBLockClient(
        AmazonDynamoDBLockClientOptions
            .builder(amazonDynamoDB, "table")
            .withPartitionKeyName(Constants.HASH_KEY)
            .withTimeUnit(TimeUnit.MILLISECONDS)
            .withLeaseDuration(leaseDuration)
            .withHeartbeatPeriod(heartBeatPeriod)
            .withCreateHeartbeatBackgroundThread(true)
            .build());

    LockItem lockItem = lockClientOne.acquireLock(lockOptions);
    Assertions.assertNotNull(lockItem, "lock acquired");

    // create new lock client which should not be able to acquire lock
    AmazonDynamoDBLockClient lockClientTwo = new AmazonDynamoDBLockClient(
        AmazonDynamoDBLockClientOptions
            .builder(amazonDynamoDB, "table")
            .withPartitionKeyName(Constants.HASH_KEY)
            .withTimeUnit(TimeUnit.MILLISECONDS)
            .withLeaseDuration(leaseDuration)
            .withHeartbeatPeriod(heartBeatPeriod)
            .withCreateHeartbeatBackgroundThread(true)
            .build());

    Thread.sleep(leaseDuration);
    boolean wasThrown = false;
    try {
      lockClientTwo.acquireLock(lockOptions);
    } catch (LockCurrentlyUnavailableException e) {
      wasThrown = true;
    }
    Assertions.assertTrue(wasThrown, "exception - expected behavior");

    // force shutdown
    Field shuttingDown = lockClientOne.getClass().getDeclaredField("shuttingDown");
    shuttingDown.setAccessible(true);
    shuttingDown.set(lockClientOne, true);

    // wait so item gets old
    Thread.sleep(leaseDuration * 3L);

    //  we would expect that lock can be acquired as nobody is sending heartbeats - but this throws exception LockCurrentlyNotAvailable 
    lockItem = lockClientTwo.acquireLock(lockOptions);

  }

Make acquireLock() reentrant

If a client already owns a lock and acquireLock() is called, the call blocks and fails.

Test case:

    @Test
    public void testAcquireLockMultipleTimes() throws InterruptedException {
        LockItem item = this.lockClientWithHeartbeating.acquireLock(AcquireLockOptions.builder("testKey1").build());
        this.lockClientWithHeartbeating.acquireLock(AcquireLockOptions.builder("testKey1").build());
        item.close();
    }

DynamoDB lock can't expired after LeaseDuration time

I am new to this DynamoDB lock client and I flow this document to implement this
https://aws.amazon.com/blogs/database/building-distributed-locks-with-the-dynamodb-lock-client/
As per this document the lock client uses client-side TTL to expire locks after the LeaseDuration time say 10sec but after 10 sec of lock acquired still lock exist.
final AmazonDynamoDB dynamoDB = AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(DYNAMODB_LOCAL_ENDPOINT)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(Regions.AP_SOUTHEAST_1)
.build();
final boolean createHeartbeatBackgroundThread = false;
final AmazonDynamoDBLockClient client = new AmazonDynamoDBLockClient(
AmazonDynamoDBLockClientOptions.builder(dynamoDB, "t2_lock")
.withTimeUnit(TimeUnit.SECONDS)
.withLeaseDuration(10L)
.withHeartbeatPeriod(1L)
.withCreateHeartbeatBackgroundThread(createHeartbeatBackgroundThread)
.withOwnerName("2")
.build());
final Optional<LockItem> lockItem;
lockItem =client.tryAcquireLock(AcquireLockOptions.builder("2").build());
// check lock is present or not System.out.printn("Is lock present "+lockItem.isPresent()); //wait for 10 sec TimeUnit.SECONDS.sleep(10); now check after 10 sec lock is present or not System.out.printn("Is lock present "+lockItem.isPresent());``

After 10 I get lock still present
Can you please help me understand If I am wrong at any point
Any comment will be appreciated.
Thank you

Creating lock table with on demand provisioning

In our usecase, we are using DynamoDB locks to keep multiple hosts from scanning the same table. Dynamo on demand is great for this solution, since locks are taken/polled infrequently (minute-hours-days), and there's no need for constant provisioning.

Currently, the client has a static method for making Dynamo tables with provisioned throughput, it would be great to make a small extension and support on demand tables.
There's a field available through the SDK ".withBillingMode(PAY_PER_REQUEST);"

Heartbeat thread working with lambda

Hi,

I was looking for implementing the DDB lock client inside a lambda. As per the lambda documentation here,

After the function and all extensions have completed, Lambda maintains the execution environment for some time in anticipation of another function invocation. In effect, Lambda freezes the execution environment. When the function is invoked again, Lambda thaws the environment for reuse.

So here, I wanted to understand, how does the heartbeat BG thread works in case of any lambda failure/timeout. Is it safe to assume that background thread will die as well, or it might be the case as it says in the documentation here, that if another lambda is executed in the same context, heartbeat thread might continue it's execution from there.

NullPointerException

I tried following the example for acquiring a lock from an item in dynamodb, but I am getting a NullPointerException, which seems to be caused by calling getS() on the String "ownerName" in the AmazonDynamoDBLockClient, at line 778. Below is a copy of my error message:

  "errorMessage": "java.lang.NullPointerException",
  "errorType": "java.lang.NullPointerException",
  "stackTrace": [
    "com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient.createLockItem(AmazonDynamoDBLockClient.java:778)",
    "com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient.getLockFromDynamoDB(AmazonDynamoDBLockClient.java:750)",
    "com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient.acquireLock(AmazonDynamoDBLockClient.java:402)",
    "com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient.tryAcquireLock(AmazonDynamoDBLockClient.java:567)",
    "com.elliemae.cpe.Hello.handleRequest(Hello.java:45)",
    "sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
    "sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)",
    "sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)",
    "java.lang.reflect.Method.invoke(Method.java:498)"
  ]
}

Here is my code that I am using, which is nearly identical to the example code.

        final boolean createHeartbeatBackgroundThread = true;
        //build the lock client
        final AmazonDynamoDBLockClient client = new AmazonDynamoDBLockClient(
                AmazonDynamoDBLockClientOptions.builder(ddbClient, "my-table")
                        .withTimeUnit(TimeUnit.SECONDS)
                        .withLeaseDuration(10L)
                        .withPartitionKeyName("key")
                        .withHeartbeatPeriod(3L)
                        .withCreateHeartbeatBackgroundThread(createHeartbeatBackgroundThread)
                        .build());

        //try to acquire a lock on the partition key "Moe"
        try {
            final Optional<LockItem> lockItem =
                    client.tryAcquireLock(AcquireLockOptions.builder("Moe").build());
            if (lockItem.isPresent()) {
                System.out.println("Acquired lock! If I die, my lock will expire in 10 seconds.");
                System.out.println("Otherwise, I will hold it until I stop heartbeating.");
                client.releaseLock(lockItem.get());
            } else {
                System.out.println("Failed to acquire lock!");
            }
            client.close();
        }
        catch(IOException | InterruptedException e){
            e.printStackTrace();
        }

The value of ownerName is definitely being set, as I used reflection to print out the value, but the problem seems to be with the getS() method. I do not think it matters, but I am using this library on aws lambda, not an ec2 instance.

Server side TTL?

This blog post mentions possible future enhancements that could use server-side TTL, any thoughts on what direction that could take?

Package is the same as DynamoDB client

The core DynamoDB client classes are located in com.amazonaws.services.dynamodbv2 and sub packages. The Lock Client classes are also located in com.amazonaws.services.dynamodbv2. This causes difficulties when using the library in an OSGi environment.
For OSGi environments each library has to define which packages are imported and exported. When the packages are the same, the Lock Client library could not define an import for that package because it provides classes by itself in the same library. This confuses the OSGi classloader.

Please move the Lock Client packages to a sub package like com.amazonaws.services.dynamodbv2.locking. Another solution is to embed the Lock Client in the Java SDK.

Heartbeat failure

Hi, We are trying to use https://github.com/awslabs/dynamodb-lock-client to do leader election and doing some test.

Our test is super simple and what we do is like:
client = new AmazonDynamoDBLockClient(
AmazonDynamoDBLockClientOptions.builder(dynamoDB, config.tableName())
.withPartitionKeyName(config.partitionKey())
.withTimeUnit(TimeUnit.MILLISECONDS)
.withLeaseDuration(1000)
.withHeartbeatPeriod(100)
.withOwnerName("test")
.withCreateHeartbeatBackgroundThread(true)
.build());
lockItem = client.tryAcquireLock(
AcquireLockOptions
.builder(config.lockValue())
.withDeleteLockOnRelease(false)
.withAdditionalTimeToWaitForLock(300)
.withTimeUnit(TimeUnit.MILLISECONDS)
.withRefreshPeriod(100)
.build());

while(true) {
Optional existing = client.getLock(config.lockValue(), Optional.empty());
Thread.sleep(300);
}

And we test above programs by just using 1 single server.
the heartbeat is using 100ms, and when program started, it works well, get the lock, and every 100ms the heartbeat thread will update the version number .
But randomly after 2 or 3 minutes, problems happened, by enabling the logs, I found the function sendHeartBeat, will throw ConditionalCheckFailedException, since it is trying to conditionally update the record, but the question is there is only single server running this, and I can not find another places trying to update the record concurrently, so what's going on here?

Upgrade to JDK17+

This code fails to build using JDK17.

For me, I would be ok if this was for the newer SDK version only.

Fencing

Cool to see pieces of this being open sourced!

You mention clock skew in the readme, but there is no discussion of "skew" in the sense of a client acquiring a lock, getting stuck doing garbage collection (or whatever), then proceeding to take an action believing that it holds a lock which has in fact expired and been acquired by someone else. Does this version of the library support something like fencing to allow downstream systems to detect and reject out-of-order operations?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.