This plugin provides a framework and interface for a synchronization mechanism that can be distributed outside the context of the app container it runs in. In today's world of horizontal computational scale and massive concurrency, it becomes increasingly difficult to synchronize operations outside the context of a single computational space (server/process/container). This plugin aims to make that easier by providing a simple service to facilitate this, as well as defining an interface for adding low level providers.
In the current release, only a provider for redis currently exists, which depends on the grails-redis plugin. Any other providers are welcome contributions.
- 0.1: Initial release
- 0.2: Adding withLock() variations for convenience to LockService impl
- 0.2.1: Minor bug fixes in configuration
- 0.2.2: Minor fix for domain class identification
Add the plugin to your BuildConfig.groovy:
plugins {
compile ":distributed-lock:0.1.0"
}
First thing is to configure your redis store. Add sample config to your Config.groovy:
grails {
redis {
host = 'localhost'
port = 6379
}
}
NOTE: Please see grails-redis for more configuration details for your redis store
Next, configure your distributed-lock options:
distributedLock {
provider {
type = RedisLockProvider // Currently the only available provider
// NOTE: Use only if not using the default redis connection
// connection = 'otherThanDefault'
}
raiseError = true //optional
defaultTimeout = 10000l //optional
defaultTTL = 1000l * 60l * 60l //optional (1 hour)
namespace = 'example-app' //optional
}
Configuration options:
-
provider: This block is used to describe your implementation provider
- type: The implementation class of a low level provider (currently only RedisLockProvider avail)
- connection: Used by redis provider to specify specific connection (if using grails-redis multi connection)
-
raiseError: Config option to throw exceptions on failures as opposed to just returning boolean status (defaults to 'true')
-
namespace: Specify a namespace for your lock keys (defaults to 'distributed-lock')
-
defaultTimeout: The default time (in millis) to wait for a lock acquire before failing (defaults to '30000')
-
defaultTTL: The TTL (in millis) for an active lock. If its not released in this much time, it will be force released when expired. Value defaults to 0 (never expires)
The plugin provides a single non transactional service that handles all lock negotiation that you can inject in any of your services
class MyService {
def lockService
def someMethod() {
def lockKey = lockService.acquireLock('/lock/a/shared/fs')
}
}
LockService Methods
- acquireLock( String lockName, Map options = null ): attempts to acquire a lock of lockName with the optional options. Returns true on success.
- acquireLockByDomain( Object domainInstance, Map options = null ): Convenience method to acquire a lock derived from a domain class instance. Returns true on success..
- releaseLock( String lockName, Map options = null ): release a lock lockName when no longer needed. Returns true on success.
- releaseLockByDomain( Object domainInstance, Map options = null ): release a lock derived from a domain class instance. Returns true on success.
- renewLock( String lockName, Map options = null ): Can renew the lease on an expiring active lock. If no ttl specified in options, lock ceases to become volatile. Returns true on success.
- renewLockByDomain( Object domainInstance, Map options = null_ ): Renew a lock derived from a domain class instance. Returns true on success.
- getLocks(): Returns a Set<String> of lock names that are currently active in the system.
Options
The optional Map allows you to override certain configuration settings just for the context of your method call. Options include:
- timeout: time in millis to wait for for the operation to complete before returning failure
- ttl: time in millis for an acquired lock to expire itself if not released
- raiseError: boolean instructing whether to throw an exception on failure or just return boolean status
Simple usages of LockService:
def lockKey = lockService.acquireLock('mylock')
if (lockKey) {
// Perform on operation we want synchronized
}
else {
println("Unable to obtain lock")
}
// try/finally to release lock
try {
def lock = lockService.acquireLock('lock2', [timeout:2000l, ttl:10000l, raiseError:false])
if (lock) {
// DO SOME SYNCHRONIZED STUFF
}
}
finally {
lockService.releaseLock('lock2',[lock: lock])
}
Threaded sample using executor plugin:
def lockService
(0..10).each { i ->
runAsync {
def lock
try {
if (lock = lockService.acquireLock('test-run', [timeout:5000l]))
println("Lock acquired for thread ${i}")
else
println("Failed to acquire lock for thread ${i}")
// Sleep for random amount of time
sleep(new Random().nextInt(1000) as Long)
}
finally {
lockService.releaseLock('test-run',[lock:lock])
}
}
}
To add additional providers is simple. Simply extend the abstract com.bertram.lock.LockProvider class and implement its abstract methods. Once the new provider is implemented, it must be added to the LockServiceConfigurer configuration method. Please submit contributions via pull request.