From e938554227735e67edd4da46915a4281524e1843 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Thu, 18 Oct 2018 18:26:26 +0100 Subject: [PATCH 1/2] locker: naive lock implementation on Redis This is the simplest implementation on top of our current locking framework. We can probably do better though as there are two types of retries happening (redisson tryLock and ours). Note that this would now pull the redisson jar by default. Signed-off-by: Pierre-Alexandre Meyer --- locker/pom.xml | 9 ++ .../locker/redis/RedisGlobalLocker.java | 87 +++++++++++++++ .../locker/redis/TestRedisGlobalLocker.java | 104 ++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 locker/src/main/java/org/killbill/commons/locker/redis/RedisGlobalLocker.java create mode 100644 locker/src/test/java/org/killbill/commons/locker/redis/TestRedisGlobalLocker.java diff --git a/locker/pom.xml b/locker/pom.xml index 7be9a49a..f5de5880 100644 --- a/locker/pom.xml +++ b/locker/pom.xml @@ -49,6 +49,11 @@ 1.0 test + + it.ozimov + embedded-redis + test + mysql mysql-connector-mxj @@ -76,6 +81,10 @@ + + org.redisson + redisson + org.slf4j slf4j-api diff --git a/locker/src/main/java/org/killbill/commons/locker/redis/RedisGlobalLocker.java b/locker/src/main/java/org/killbill/commons/locker/redis/RedisGlobalLocker.java new file mode 100644 index 00000000..ac4a33c5 --- /dev/null +++ b/locker/src/main/java/org/killbill/commons/locker/redis/RedisGlobalLocker.java @@ -0,0 +1,87 @@ +/* + * Copyright 2014-2018 Groupon, Inc + * Copyright 2014-2018 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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. + */ + +package org.killbill.commons.locker.redis; + +import java.util.concurrent.TimeUnit; + +import org.killbill.commons.locker.GlobalLock; +import org.killbill.commons.locker.GlobalLocker; +import org.killbill.commons.locker.GlobalLockerBase; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; + +public class RedisGlobalLocker extends GlobalLockerBase implements GlobalLocker { + + private final RedissonClient redissonClient; + + public RedisGlobalLocker(final RedissonClient redissonClient) { + super(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + this.redissonClient = redissonClient; + } + + @Override + public synchronized boolean isFree(final String service, final String lockKey) { + final String lockName = getLockName(service, lockKey); + final RLock redisLock = redissonClient.getLock(lockName); + return !redisLock.isLocked(); + } + + @Override + protected synchronized GlobalLock doLock(final String lockName) { + final RLock redisLock = redissonClient.getLock(lockName); + if (redisLock.isLocked()) { + return null; + } + + final boolean acquired; + try { + // waitTime=1ms (retry done ourselves) + // leaseTime=5min + acquired = redisLock.tryLock(1, 300000, TimeUnit.MILLISECONDS); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + + if (!acquired) { + return null; + } else if (redisLock.getHoldCount() > 1) { + // Someone beat us to it? + redisLock.forceUnlock(); + return null; + } + + final GlobalLock lock = new GlobalLock() { + @Override + public void release() { + if (lockTable.releaseLock(lockName)) { + redisLock.forceUnlock(); + } + } + }; + + lockTable.createLock(lockName, lock); + + return lock; + } + + @Override + protected String getLockName(final String service, final String lockKey) { + return service + "-" + lockKey; + } +} diff --git a/locker/src/test/java/org/killbill/commons/locker/redis/TestRedisGlobalLocker.java b/locker/src/test/java/org/killbill/commons/locker/redis/TestRedisGlobalLocker.java new file mode 100644 index 00000000..bb44de9e --- /dev/null +++ b/locker/src/test/java/org/killbill/commons/locker/redis/TestRedisGlobalLocker.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-2018 Groupon, Inc + * Copyright 2014-2018 The Billing Project, LLC + * + * The Billing Project licenses this file to you 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. + */ + +package org.killbill.commons.locker.redis; + +import java.io.IOException; +import java.util.UUID; + +import org.killbill.commons.locker.GlobalLock; +import org.killbill.commons.locker.GlobalLocker; +import org.killbill.commons.locker.LockFailedException; +import org.killbill.commons.request.Request; +import org.killbill.commons.request.RequestData; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import redis.embedded.RedisServer; + +public class TestRedisGlobalLocker { + + private RedissonClient redissonClient; + private GlobalLocker locker; + private RedisServer redisServer; + + @BeforeMethod(groups = "slow") + public void beforeMethod() throws Exception { + redisServer = new RedisServer(56379); + redisServer.start(); + + final Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:56379").setConnectionMinimumIdleSize(10); + redissonClient = Redisson.create(config); + locker = new RedisGlobalLocker(redissonClient); + Request.resetPerThreadRequestData(); + } + + @AfterMethod(groups = "slow") + public void afterMethod() throws Exception { + redissonClient.shutdown(); + Request.resetPerThreadRequestData(); + redisServer.stop(); + } + + @Test(groups = "slow") + public void testSimpleLocking() throws IOException, LockFailedException { + final String serviceLock = "MY_AWESOME_LOCK"; + final String lockName = UUID.randomUUID().toString(); + + final GlobalLock lock = locker.lockWithNumberOfTries(serviceLock, lockName, 3); + Assert.assertFalse(locker.isFree(serviceLock, lockName)); + + boolean gotException = false; + try { + locker.lockWithNumberOfTries(serviceLock, lockName, 1); + } catch (final LockFailedException e) { + gotException = true; + } + Assert.assertTrue(gotException); + + lock.release(); + Assert.assertTrue(locker.isFree(serviceLock, lockName)); + } + + @Test(groups = "slow") + public void testReentrantLock() throws IOException, LockFailedException { + final String serviceLock = "MY_SHITTY_LOCK"; + final String lockName = UUID.randomUUID().toString(); + + final String requestId = "12345"; + + Request.setPerThreadRequestData(new RequestData(requestId)); + + final GlobalLock lock = locker.lockWithNumberOfTries(serviceLock, lockName, 3); + Assert.assertFalse(locker.isFree(serviceLock, lockName)); + + // Re-aquire the createLock with the same requestId, should work + final GlobalLock reentrantLock = locker.lockWithNumberOfTries(serviceLock, lockName, 1); + + lock.release(); + Assert.assertFalse(locker.isFree(serviceLock, lockName)); + + reentrantLock.release(); + Assert.assertTrue(locker.isFree(serviceLock, lockName)); + } +} From f5e03cfb6a0b3abba65db2268c69ed60935e5ee5 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Fri, 19 Oct 2018 07:39:21 +0100 Subject: [PATCH 2/2] pom.xml: update to killbill-oss-parent 0.142.5 Signed-off-by: Pierre-Alexandre Meyer --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0f88ee04..350e6894 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ killbill-oss-parent org.kill-bill.billing - 0.142.5-SNAPSHOT + 0.142.5 org.kill-bill.commons killbill-commons