INT-2502, INT-1117 Improve Group Locking

Add initial support for Global lock registry. Fix race condition described in INT-2502.

INT-2502 addressed PR comments by @garyrussell

INT-2502 polishing
This commit is contained in:
Oleg Zhurakousky
2012-03-28 12:11:29 -04:00
committed by Gary Russell
parent f5908ec876
commit 4375cd9c8c
7 changed files with 409 additions and 69 deletions

View File

@@ -17,8 +17,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -37,6 +36,8 @@ import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageGroup;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.util.DefaultLockRegistry;
import org.springframework.integration.util.LockRegistry;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -83,15 +84,16 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
private boolean sendPartialResultOnExpiry = false;
private final Object correlationLocksMonitor = new Object();
private final ConcurrentMap<Object, Object> locks = new ConcurrentHashMap<Object, Object>();
private volatile boolean sequenceAware = false;
private volatile LockRegistry lockRegistry = new DefaultLockRegistry();
private boolean lockRegistrySet = false;
public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store,
CorrelationStrategy correlationStrategy, ReleaseStrategy releaseStrategy) {
Assert.notNull(processor);
Assert.notNull(store);
setMessageStore(store);
this.outputProcessor = processor;
@@ -110,6 +112,12 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
this(processor, new SimpleMessageStore(0), null, null);
}
public void setLockRegistry(LockRegistry lockRegistry) {
Assert.isTrue(!lockRegistrySet, "'this.lockRegistry' can not be reset once its been set");
Assert.notNull("'lockRegistry' must not be null");
this.lockRegistry = lockRegistry;
this.lockRegistrySet = true;
}
public void setMessageStore(MessageGroupStore store) {
this.messageStore = store;
@@ -183,9 +191,10 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
}
// TODO: INT-1117 - make the lock global?
Object lock = getLock(correlationKey);
Lock lock = this.lockRegistry.obtain(correlationKey);
synchronized (lock) {
lock.lockInterruptibly();
try {
MessageGroup messageGroup = messageStore.getMessageGroup(correlationKey);
if (this.sequenceAware){
messageGroup = new SequenceAwareMessageGroup(messageGroup);
@@ -206,10 +215,6 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
// Always clean up even if there was an exception
// processing messages
this.afterRelease(messageGroup, completedMessages);
synchronized(correlationLocksMonitor){
locks.remove(messageGroup.getGroupId());
}
}
}
}
@@ -217,9 +222,11 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
discardChannel.send(message);
}
}
finally {
lock.unlock();
}
}
/**
* Allows you to provide additional logic that needs to be performed after the MessageGroup was released.
* @param group
@@ -230,32 +237,34 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
private final boolean forceComplete(MessageGroup group) {
Object correlationKey = group.getGroupId();
Object lock = getLock(correlationKey);
synchronized (lock) {
if (group.size() > 0) {
try {
if (releaseStrategy.canRelease(group)) {
this.completeGroup(correlationKey, group);
}
else {
this.expireGroup(correlationKey, group);
Lock lock = this.lockRegistry.obtain(correlationKey);
try {
lock.lockInterruptibly();
try {
if (group.size() > 0) {
try {
if (releaseStrategy.canRelease(group)) {
this.completeGroup(correlationKey, group);
}
else {
this.expireGroup(correlationKey, group);
}
}
finally {
this.remove(group);
}
return true;
}
finally {
this.remove(group);
}
return true;
}
return false;
finally {
lock.unlock();
}
}
}
private Object getLock(Object correlationKey) {
synchronized(correlationLocksMonitor){
locks.putIfAbsent(correlationKey, new Object());
return locks.get(correlationKey);
catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new MessagingException("Thread was interrupted while trying to obtain lock");
}
return false;
}
void remove(MessageGroup group) {
@@ -411,5 +420,4 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
return false;
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed 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.springframework.integration.util;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.util.Assert;
/**
* Default implementation of {@link LockRegistry} which uses Masked Hashcode algorithm to obtain locks.
* When an instance of this class is created and array of {@link Lock} objects is created. The length of
* the array is based on the 'mask' parameter passed in the constructor. The default mask is 0xFF which will create
* and array consisting of 256 {@link ReentrantLock} instances.
* When the {@link #obtain(Object)} method is called with the lockKey (e.g., Object) the index of the {@link Lock}
* is determined by masking the object's hashCode (e.g., object.hashCode & mask) and the {@link Lock} is returned.
*
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 2.1.1
*
*/
public final class DefaultLockRegistry implements LockRegistry {
private final Lock[] lockTable;
private final int mask;
/**
* Constructs a DefaultLockRegistry with the default
* mask 0xFF with 256 locks.
*/
public DefaultLockRegistry(){
this(0xFF);
}
/**
* Constructs a DefaultLockRegistry with the supplied
* mask - the mask must have a value (2**n) - 1 where n
* is 1 to 31, creating a hash of 2**n locks.
* <p> Examples:
* <li>0x3ff (1023) - 1024 locks</li>
* <li>0xfff (4095) - 4096 locks</li>
* <p>
* @param mask
*/
public DefaultLockRegistry(int mask){
String bits = Integer.toBinaryString(mask);
Assert.isTrue(bits.lastIndexOf('0') < bits.indexOf('1'), "Mask must be a power of 2 - 1");
this.mask = mask;
int arraySize = this.mask+1;
lockTable = new ReentrantLock[arraySize];
for (int i = 0; i < arraySize; i++) {
lockTable[i] = new ReentrantLock();
}
}
/**
* Obtains a lock by masking the lockKey's hashCode() with
* the mask and using the result as an index to the lock table.
* @param lockKey the object used to derive the lock index.
*/
public Lock obtain(Object lockKey) {
Assert.notNull(lockKey, "'lockKey' must not be null");
Integer lockIndex = lockKey.hashCode() & this.mask;
return this.lockTable[lockIndex];
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed 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.springframework.integration.util;
import java.util.concurrent.locks.Lock;
/**
* Strategy for maintaining a registry of shared locks
*
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 2.1.1
*/
public interface LockRegistry {
/**
* Obtains the lock associated with the parameter object.
* @param lockRoot The object with which the lock is associated.
* @return The associated lock.
*/
Lock obtain(Object lockKey);
}