SPRNET-1337

-Applied recommended fix from forum post
-Added new unit test to validate that handlers are acting in parallel once new patch introduced
This commit is contained in:
sbohlen
2010-08-27 17:00:05 +00:00
parent 7d523c654a
commit 7626d3f38e
8 changed files with 342 additions and 59 deletions

View File

@@ -1,19 +1,19 @@
#region License
/*
* Copyright <20> 2002-2005 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.
/*
* Copyright <20> 2002-2005 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.
*/
#endregion
@@ -95,6 +95,8 @@ namespace Spring.Expressions
{
string methodName = this.getText();
object[] argValues = ResolveArguments(evalContext);
ICollectionProcessor localCollectionProcessor = null;
IMethodCallProcessor methodCallProcessor = null;
// resolve method, if necessary
lock (this)
@@ -102,35 +104,24 @@ namespace Spring.Expressions
// check if it is a collection and the methodname denotes a collection processor
if ((context == null || context is ICollection))
{
ICollectionProcessor localCollectionProcessor;
// predefined collection processor?
localCollectionProcessor = (ICollectionProcessor) collectionProcessorMap[methodName];
localCollectionProcessor = (ICollectionProcessor)collectionProcessorMap[methodName];
// user-defined collection processor?
if (localCollectionProcessor == null && evalContext.Variables != null)
{
localCollectionProcessor = evalContext.Variables[methodName] as ICollectionProcessor;
}
if (localCollectionProcessor != null)
{
return localCollectionProcessor.Process((ICollection) context, argValues);
}
}
// try extension methods
IMethodCallProcessor methodCallProcessor = (IMethodCallProcessor)extensionMethodProcessorMap[methodName];
methodCallProcessor = (IMethodCallProcessor)extensionMethodProcessorMap[methodName];
{
// user-defined extension method processor?
if (methodCallProcessor == null && evalContext.Variables != null)
{
methodCallProcessor = evalContext.Variables[methodName] as IMethodCallProcessor;
}
if (methodCallProcessor != null)
{
return methodCallProcessor.Process(context, argValues);
}
}
// try instance method
@@ -148,18 +139,28 @@ namespace Spring.Expressions
Initialize(methodName, argValues, context);
initialized = true;
}
if (cachedInstanceMethod != null)
{
object[] paramValues = (cachedIsParamArray)
? ReflectionUtils.PackageParamArray(argValues, argumentCount, paramArrayType)
: argValues;
return cachedInstanceMethod.Invoke(context, paramValues);
}
}
}
throw new ArgumentException(string.Format("Method '{0}' with the specified number and types of arguments does not exist.", methodName));
if (localCollectionProcessor != null)
{
return localCollectionProcessor.Process((ICollection)context, argValues);
}
else if (methodCallProcessor != null)
{
return methodCallProcessor.Process(context, argValues);
}
else if (cachedInstanceMethod != null)
{
object[] paramValues = (cachedIsParamArray)
? ReflectionUtils.PackageParamArray(argValues, argumentCount, paramArrayType)
: argValues;
return cachedInstanceMethod.Invoke(context, paramValues);
}
else
{
throw new ArgumentException(string.Format("Method '{0}' with the specified number and types of arguments does not exist.", methodName));
}
}
private int CalculateMethodHash(Type contextType, object[] argValues)
@@ -268,21 +269,21 @@ namespace Spring.Expressions
}
// used to calculate signature hash while caring for arg positions
private static readonly int[] s_primes =
{
17, 19, 23, 29
, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71
, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113
, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173
, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229
, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281
, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349
, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409
, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463
, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541
, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601
, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659
, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733
private static readonly int[] s_primes =
{
17, 19, 23, 29
, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71
, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113
, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173
, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229
, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281
, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349
, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409
, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463
, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541
, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601
, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659
, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733
};
}
}

View File

@@ -81,11 +81,11 @@ namespace Spring.Messaging.Listener
q.ConvertAndSend("Goodbye World 1");
Assert.AreEqual(0, listener.MessageCount, "PRECONDITION FAILURE: Unable to send the message!");
distributedTxMessageListenerContainer.Start();
Thread.Sleep(waitInMillis);
distributedTxMessageListenerContainer.Stop();
distributedTxMessageListenerContainer.Shutdown();
Thread.Sleep(2500);
@@ -105,6 +105,8 @@ namespace Spring.Messaging.Listener
//must match the retry count in the object registration for test to pass!
const int EXCEPTION_QUEUE_RETRY_COUNT = 2;
int expectedMessageCount = MESSAGE_COUNT + (MESSAGE_COUNT * EXCEPTION_QUEUE_RETRY_COUNT);
MessageQueueTemplate q = applicationContext["queueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
@@ -115,13 +117,22 @@ namespace Spring.Messaging.Listener
Assert.AreEqual(0, listener.MessageCount);
System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
timer.Start();
distributedTxMessageListenerContainer.Start();
//this test needs to wait somewhat longer than the others in order to consistently pass so
//artificially inflate the waiting period before attempting subsequent asserts:
Thread.Sleep((int)(waitInMillis * 1.5));
while (listener.MessageCount < expectedMessageCount)
{
if (timer.ElapsedMilliseconds > 60000)
Assert.Fail("Did not receive expected number of messages within the permitted time limit.");
}
Assert.AreEqual(MESSAGE_COUNT + (MESSAGE_COUNT * EXCEPTION_QUEUE_RETRY_COUNT), listener.MessageCount);
timer.Stop();
System.Diagnostics.Debug.WriteLine("elapsed time = " + timer.ElapsedMilliseconds);
Assert.AreEqual(expectedMessageCount, listener.MessageCount);
distributedTxMessageListenerContainer.Stop();
distributedTxMessageListenerContainer.Shutdown();

View File

@@ -0,0 +1,120 @@
#region License
/*
* Copyright <20> 2002-2007 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.
*/
#endregion
#region Imports
using System.Threading;
using NUnit.Framework;
using Spring.Messaging.Core;
using Spring.Testing.NUnit;
using System.Diagnostics;
using System;
#endregion
namespace Spring.Messaging.Listener
{
/// <summary>
/// This class contains tests for
/// </summary>
/// <author>Mark Pollack</author>
/// <version>$Id:$</version>
[TestFixture]
public class MultiThreadedNonTransactionalMessageListenerContainerTests : AbstractDependencyInjectionSpringContextTests
{
private NonTransactionalMessageListenerContainer container;
private WaitingHandler listener;
private SimpleExceptionHandler exceptionHandler;
[SetUp]
public override void SetUp()
{
MessageQueueUtils.RecreateMessageQueue(@".\Private$\testqueue", false);
MessageQueueUtils.RecreateMessageQueue(@".\Private$\testresponsequeue", false);
base.SetUp();
}
public SimpleExceptionHandler ExceptionHandler
{
set { exceptionHandler = value; }
}
public NonTransactionalMessageListenerContainer Container
{
get { return container; }
set { container = value; }
}
public WaitingHandler Listener
{
get { return listener; }
set { listener = value; }
}
[Test]
public void Test()
{
System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
MessageQueueTemplate q = applicationContext["testQueueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
q.ConvertAndSend("Hello World 1");
q.ConvertAndSend("Hello World 2");
q.ConvertAndSend("Hello World 3");
q.ConvertAndSend("Hello World 4");
q.ConvertAndSend("Hello World 5");
//Reset the state so that running all tests together will succeed.
exceptionHandler.MessageCount = 0;
Assert.AreEqual(0, listener.MessageCount);
timer.Start();
container.Start();
while (listener.MessageCount < 5)
{
//provide an exit if the test is completely over-length
if (timer.ElapsedMilliseconds > 120000)
Assert.Fail("Did not receive expected number of messages with the expected time-limit!");
}
timer.Stop();
container.Stop();
container.Shutdown();
Debug.WriteLine(String.Format("Elapsed Milliseconds: {0}", timer.ElapsedMilliseconds));
Assert.Less(timer.ElapsedMilliseconds, 50000);
Assert.AreEqual(5, listener.MessageCount);
Assert.AreEqual(0, exceptionHandler.MessageCount);
}
protected override string[] ConfigLocations
{
get { return new string[] { "assembly://Spring.Messaging.Tests/Spring.Messaging.Listener/MultiThreadedNonTransactionalMessageListenerContainerTests.xml" }; }
}
}
}

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database">
<db:provider id="DbProvider"
provider="System.Data.SqlClient"
connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Spring;Persist Security Info=True;User ID=springqa;Password=springqa"/>
<object id="adoTransactionManager"
type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data">
<property name="DbProvider" ref="DbProvider"/>
</object>
<object id='msmqTestQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testqueue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTestQueueLabel'/>
</object>
</property>
</object>
<object id='msmqTestResponseQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testresponsequeue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTestResponseQueueLabel'/>
</object>
</property>
</object>
<object id='testremotequeue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='FormatName:Direct=OS:RAVEN\Private$\testqueue'/>
<property name='RemoteQueue' value="true"/>
</object>
<object id="testRemoteTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="DefaultMessageQueueObjectName" value="testremotequeue"/>
<property name="MessageConverterObjectName" value="binaryMessageConverter"/>
</object>
<object id="testQueueTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="DefaultMessageQueueObjectName" value="msmqTestQueue"/>
<property name="MessageConverterObjectName" value="binaryMessageConverter"/>
</object>
<!-- Message Converters -->
<object id="messageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"
singleton="false">
<property name="TargetTypes" value="System.String"/>
</object>
<object id="binaryMessageConverter" type="Spring.Messaging.Support.Converters.BinaryMessageConverter, Spring.Messaging" singleton="false">
</object>
<object id="nonTransactionalMessageListenerContainer" type="Spring.Messaging.Listener.NonTransactionalMessageListenerContainer, Spring.Messaging">
<property name="MessageQueueObjectName" value="testremotequeue"/>
<property name="MaxConcurrentListeners" value="5"/>
<property name="ListenerTimeLimit" value="20s"/>
<property name="MessageListener" ref="messageListenerAdapter"/>
<property name="ExceptionHandler" ref="exceptionHandler"/>
<property name="AutoStartup" value="false"/>
</object>
<!-- Adapter -->
<object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging">
<property name="DefaultResponseQueueName" value="msmqTestResponseQueue"/>
<property name="MessageConverterObjectName" value="binaryMessageConverter"/>
<property name="HandlerObject" ref="waitingHandler"/>
</object>
<!-- Message and Exception Handlers -->
<object id="waitingHandler" type="Spring.Messaging.Listener.WaitingHandler, Spring.Messaging.Tests">
</object>
<object id="exceptionHandler" type="Spring.Messaging.Listener.SimpleExceptionHandler, Spring.Messaging.Tests">
</object>
</objects>

View File

@@ -0,0 +1,49 @@
using System;
using Common.Logging;
using System.Threading;
namespace Spring.Messaging.Listener
{
public class WaitingHandler
{
#region Logging
private static readonly ILog LOG = LogManager.GetLogger(typeof(WaitingHandler));
#endregion
private int messageCount;
private string stateVariable;
public WaitingHandler()
{
this.stateVariable = "hello";
}
public WaitingHandler(string stateVariable)
{
this.stateVariable = stateVariable;
}
public int MessageCount
{
get { return messageCount; }
set { messageCount = value; }
}
public string HandleMessage(string msgTxt)
{
LOG.Debug(String.Format("Received text = [{0}]", msgTxt));
LOG.Debug("constructor set state string = " + stateVariable);
Thread.Sleep(10000);
messageCount++;
LOG.Debug("Message listener count = " + messageCount);
return msgTxt + " - processed!";
}
}
}

View File

@@ -75,6 +75,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Listener\MultiThreadedNonTransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Listener\TransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\queue-context.xml" />
<EmbeddedResource Include="Messaging\Core\MessageQueueTemplateTests.xml" />
@@ -87,11 +88,13 @@
<ItemGroup>
<Compile Include="Messaging\Core\MessageQueueMetadataCacheTests.cs" />
<Compile Include="Messaging\Core\MessageQueueUtils.cs" />
<Compile Include="Messaging\Core\WaitingHander.cs" />
<Compile Include="Messaging\Core\ThreadingTests.cs" />
<Compile Include="Messaging\Listener\Adapter\MessageListenerAdapterTests.cs" />
<Compile Include="Messaging\Listener\DistributedTxMessageListenerContainerTests.cs" />
<Compile Include="Messaging\Listener\LoggingExceptionHandler.cs" />
<Compile Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.cs">
<Compile Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.cs" />
<Compile Include="Messaging\Listener\MultiThreadedNonTransactionalMessageListenerContainerTests.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Messaging\Listener\SimpleExceptionHandler.cs" />

View File

@@ -76,6 +76,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Listener\MultiThreadedNonTransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Listener\TransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\queue-context.xml" />
<EmbeddedResource Include="Messaging\Core\MessageQueueTemplateTests.xml" />
@@ -88,13 +89,15 @@
<ItemGroup>
<Compile Include="MessagingCompilerOptionsTests.cs" />
<Compile Include="MessagingExceptionTests.cs" />
<Compile Include="Messaging\Listener\WaitingHandler.cs" />
<Compile Include="Messaging\Core\MessageQueueMetadataCacheTests.cs" />
<Compile Include="Messaging\Core\MessageQueueUtils.cs" />
<Compile Include="Messaging\Core\ThreadingTests.cs" />
<Compile Include="Messaging\Listener\Adapter\MessageListenerAdapterTests.cs" />
<Compile Include="Messaging\Listener\DistributedTxMessageListenerContainerTests.cs" />
<Compile Include="Messaging\Listener\LoggingExceptionHandler.cs" />
<Compile Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.cs">
<Compile Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.cs" />
<Compile Include="Messaging\Listener\MultiThreadedNonTransactionalMessageListenerContainerTests.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Messaging\Listener\SimpleExceptionHandler.cs" />

View File

@@ -56,6 +56,8 @@
<ItemGroup>
<Compile Include="Messaging\Core\MessageQueueTemplateTests.cs" />
<Compile Include="Messaging\Core\MessageQueueUtils.cs" />
<Compile Include="Messaging\Listener\WaitingHandler.cs" />
<Compile Include="Messaging\Listener\MultiThreadedNonTransactionalMessageListenerContainerTests.cs" />
<Compile Include="Messaging\Support\MessageQueueFactoryObjectTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
@@ -88,6 +90,9 @@
<EmbeddedResource Include="Messaging\Core\MessageQueueTemplateTests.xml" />
<EmbeddedResource Include="Messaging\Listener\DistributedTxMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Core\MessageQueueMetadataCacheTests.xml" />
<EmbeddedResource Include="Messaging\Listener\MultiThreadedNonTransactionalMessageListenerContainerTests.xml">
<SubType>Designer</SubType>
</EmbeddedResource>
<Content Include="Spring.Messaging.Tests.dll.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>