Hone logo
Hone
Problems

Distributed Lock Implementation in Go

Distributed locks are crucial for coordinating access to shared resources across multiple processes or machines. This challenge asks you to implement a basic distributed lock using Redis as the underlying storage mechanism. A robust distributed lock ensures that only one client can access a critical section of code at a time, preventing data corruption and race conditions in distributed systems.

Problem Description

You are tasked with creating a DistributedLock struct in Go that provides methods for acquiring and releasing a lock. The lock should be implemented using Redis for persistence and coordination. The DistributedLock struct should manage a unique lock identifier (a string) and a Redis client connection.

Key Requirements:

  • Acquire Lock: The Acquire() method should attempt to acquire the lock. It should use the SETNX (SET if Not eXists) Redis command to atomically set the lock identifier in Redis. A timeout parameter should be provided to allow the client to wait for a specified duration before giving up on acquiring the lock. If the lock is acquired successfully, the method should return true. If the lock is already held or the timeout expires, it should return false.
  • Release Lock: The Release() method should release the lock. It should use the DEL (Delete) Redis command to remove the lock identifier from Redis. It's crucial to ensure that the client releasing the lock is the same client that acquired it. To achieve this, the Release() method should use a Lua script to atomically check if the lock identifier exists and if the value associated with the identifier matches the client's unique identifier before deleting it. This prevents one client from accidentally releasing another client's lock.
  • Unique Identifier: Each client attempting to acquire the lock should have a unique identifier (a string). This identifier is used to prevent accidental lock release by other clients. The DistributedLock struct should store this identifier.
  • Redis Client: The DistributedLock struct should accept a Redis client connection as part of its initialization.

Expected Behavior:

  • Multiple clients attempting to acquire the lock concurrently should only allow one client to succeed.
  • A client that successfully acquires the lock should be able to release it.
  • A client that does not acquire the lock should not be able to release it.
  • The lock should be persistent across restarts of the Redis server (assuming Redis persistence is enabled).

Edge Cases to Consider:

  • Timeout: What happens if the client waits for the timeout period and still cannot acquire the lock?
  • Redis Failure: How should the code handle temporary Redis connection errors? (For simplicity, you can assume the Redis client handles connection errors and retries.)
  • Race Conditions: Ensure the release operation is atomic to prevent race conditions where a client releases a lock that has been acquired by another client.

Examples

Example 1:

Input: Client A acquires lock with identifier "clientA", timeout = 5 seconds. Client B attempts to acquire the same lock with identifier "clientB", timeout = 5 seconds.
Output: Client A acquires the lock (returns true), Client B fails to acquire the lock (returns false).
Explanation: Only one client can acquire the lock at a time.

Example 2:

Input: Client A acquires lock with identifier "clientA". After some time, Client A releases the lock. Client B then attempts to acquire the lock.
Output: Client A releases the lock (returns true), Client B acquires the lock (returns true).
Explanation: After the lock is released, another client can acquire it.

Example 3: (Edge Case - Incorrect Release)

Input: Client A acquires lock with identifier "clientA". Client B attempts to release the lock with identifier "clientA" but uses a different identifier "clientB".
Output: Client A releases the lock (returns true), Client B fails to release the lock (returns false).
Explanation: The release operation uses a Lua script to ensure that only the client that acquired the lock can release it.

Constraints

  • The Redis client should be of type *redis.Client from the github.com/go-redis/redis/v8 package.
  • The lock identifier string should be no longer than 64 characters.
  • The timeout value should be in seconds (integer).
  • The Lua script for releasing the lock must be atomic.
  • Assume Redis is available and reachable. Error handling for Redis connection failures is not required for this challenge.

Notes

  • Focus on the core logic of acquiring and releasing the lock. Error handling and advanced features (e.g., lock extensions, heartbeat mechanisms) are not required for this challenge.
  • The Lua script is crucial for ensuring atomic release. Carefully consider the script's logic to prevent race conditions.
  • Consider using a unique identifier generation strategy for your clients (e.g., UUID). For simplicity, you can hardcode identifiers for testing purposes.
  • The SETNX command is key to the acquire operation. Understand how it works atomically.
  • The DEL command is used to release the lock. The Lua script ensures that only the correct client can delete the key.
Loading editor...
go