activemq-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From semog <e.se...@gmail.com>
Subject Re: ActiveMQ.Net: Client does not include timestamp when calculating expiration
Date Thu, 30 Aug 2007 17:34:25 GMT

Hi Peter,

I have just spent the last week diagnosing and fixing this very problem in
the .NET client.  It took me a long time to figure out exactly where the
problem was.  There seems to be a disconnect between the concept of "time to
live" and "expiration".  "Time to live" is a relative timespan, while
"expiration" is an absolute date/time.  In addition, there is a bug in the
DateUtils class when converting between .NET DateTime and Java dates.  Here
is the fix that I have come up with.  Basically, I fixed the date utility
conversion functions.  I then made the concept of "time to live" transparent
to the client API.  ActiveMQ seems to require an absolute "expiration" time,
so I hid the translation of that inside the ActiveMQMessage class. 
Following are the gist of my changes.  I will show the key parts that you
can add, but you may have to change some supplementary files to make the
change complete, but those supplementary changes should be very obvious
(i.e., adding a declaration, or changing the NMSExpiration to
NMSTimeToLive).  Try these changes and see if they work for you:

Here is my new implementation of the DateUtils class.  This is a complete
drop-in replacement.  The key fix here was the old implementation mixed
calculating ticks with millisecends.  Ticks are defined as 100-nanosecond
increments, a calculation error factor of 10,000:

	internal class DateUtils
	{
		/// <summary>
		/// The start of the Windows epoch
		/// </summary>
		public static readonly DateTime windowsEpoch = new DateTime(1601, 1, 1, 0,
0, 0, 0);
		/// <summary>
		/// The start of the Java epoch
		/// </summary>
		public static readonly DateTime javaEpoch = new DateTime(1970, 1, 1, 0, 0,
0, 0);
		
		/// <summary>
		/// The difference between the Windows epoch and the Java epoch
		/// in milliseconds.
		/// </summary>
		public static readonly long epochDiff; /* = 1164447360000L; */

		static DateUtils()
		{
			epochDiff = (javaEpoch.ToFileTime() - windowsEpoch.ToFileTime())
							/ TimeSpan.TicksPerMillisecond;
		}

		public static long ToJavaTime(DateTime dateTime)
		{
			return (dateTime.ToFileTime() / TimeSpan.TicksPerMillisecond) -
epochDiff;
		}

		public static DateTime ToDateTime(long javaTime)
		{
			return DateTime.FromFileTime((javaTime + epochDiff) *
TimeSpan.TicksPerMillisecond);
		}
	}

In IMessage.cs, change the interface declaration name for NMSExpiration to
NMSTimeToLive.  In the ActiveMQMessage.cs file, add the following member
variable to ActiveMQMessage:

		protected DateTime expirationBaseTime;

In the same ActiveMQMessage.cs file, change the definition for NMSExpiration
into the following definition for NMSTimeToLive (renaming the property field
at the same time):

        /// <summary>
        /// The time in milliseconds that this message should expire in
        /// </summary>
		public TimeSpan NMSTimeToLive
		{
			get {
				if(0 != Expiration)
				{
					DateTime expirationTime = DateUtils.ToDateTime(Expiration);
					return expirationTime - expirationBaseTime;
				}
				else
				{
					return TimeSpan.FromMilliseconds(0);
				}
			}
			set {
				expirationBaseTime = DateTime.UtcNow;
				Expiration = DateUtils.ToJavaTime(expirationBaseTime + value);
			}
		}

I also changed the NMSTimeStamp property as follows.  I added a setter
property, which makes things much easier on the client side:

        /// <summary>
        /// The timestamp the broker added to the message
        /// </summary>
        public DateTime NMSTimestamp
        {
            get {
                return DateUtils.ToDateTime(Timestamp);
            }
            set {
                Timestamp = DateUtils.ToJavaTime(value);
            }
        }

Now, in the ActiveMQ MessageProducer.cs file, change the Send(...) function
as follows:

		protected void Send(IDestination destination, IMessage message, bool
persistent, byte priority, TimeSpan timeToLive, bool specifiedTimeToLive)
		{
			ActiveMQMessage activeMessage = (ActiveMQMessage)message;

			if (!disableMessageID)
			{
				MessageId id = new MessageId();
				id.ProducerId = info.ProducerId;
				lock (this)
				{
					id.ProducerSequenceId = ++messageCounter;
				}

				activeMessage.MessageId = id;
			}

			activeMessage.ProducerId = info.ProducerId;
			activeMessage.FromDestination = destination;
			activeMessage.NMSPersistent = persistent;
			activeMessage.NMSPriority = priority;

			if (session.Transacted)
			{
				session.DoStartTransaction();
				activeMessage.TransactionId = session.TransactionContext.TransactionId;
			}

			if (specifiedTimeToLive)
			{
				activeMessage.NMSTimeToLive = timeToLive;
			}

			if (!disableMessageTimestamp)
			{
				activeMessage.NMSTimestamp = DateTime.UtcNow;
			}

			session.DoSend(activeMessage);
		}

Notice that the setting of the NMSTimestamp property does not require
conversion to Java time from .NET time format, as this is taken care of
inside the new setter property.  At any level above this where you may have
explicitly set the NMSExpiration property of the message, will need to be
changed to NMSTimeToLive.  The NMSTimeToLive property is now correctly and
consistently a timespan, instead of an absolute date/time.  These changes
are much more logical to me, and I will continue with my version of the
client.  These changes allow my .NET clients to deal compleletly in .NET
date/time format and the conversion to/from Java date/time format is hidden.

Like I mentioned, you will need to make some trivial changes to MSMQ
implementation and one or two test cases, but those are simple renames.  I
hope that this can save you some time.  Since I am new to this list, I
haven't found the directions on how to submit these changes back for
possible commit into the actual source code.  Perhaps this is the first step
in that process.

Thanks.  Please let me know if you have any questions or need further
assistance in this area.

- Jim Gomes



PeterNilsson wrote:
> 
> Hi,
> 
> We have just started setting timeToLive when sending messages with the
> ActiveMQ.Net client. However we get an exception when sending:
> 
> mscorlib.dll!System.DateTime.ToFileTimeUtc() + 0xad bytes	
> mscorlib.dll!System.DateTime.ToFileTime() + 0x1c bytes	
> NMS.ActiveMQ.DLL!ActiveMQ.Util.DateUtils.ToJavaTime(System.DateTime
> dateTime = {0001-01-01 00:00:05}) Line 46 + 0x8 bytes	C#
> NMS.ActiveMQ.DLL!ActiveMQ.Util.DateUtils.ToJavaTime(System.TimeSpan
> timeToLive = {00:00:05}) Line 41 + 0x35 bytes	C#
> NMS.ActiveMQ.DLL!ActiveMQ.MessageProducer.Send(NMS.IDestination
> destination = {queue://test}, NMS.IMessage message =
> {ActiveMQBytesMessage[ ProducerId= Destination= TransactionId=
> OriginalDestination= MessageId=MessageId[ ProducerId=ProducerId[
> ConnectionId=a5e10bc2-46ce-4b5b-be85-d1ed6e0c7575 Value=1 SessionId=1 ]
> ProducerSequenceId=1 BrokerSequenceId=0 ] OriginalTransactionId= GroupID=
> GroupSequence=0 CorrelationId=0 Persistent=False Expiration=0 Priority=0
> ReplyTo=temp-queue://a5e10bc2-46ce-4b5b-be85-d1ed6e0c7575:1
> Timestamp=128310953802143903 Type= Content=System.Byte[]
> MarshalledProperties= DataStructure= TargetConsumerId= Compressed=False
> RedeliveryCounter=0 BrokerPath= Arrival=0 UserID= RecievedByDFBridge=False
> Droppable=False ]}, bool persistent = false, byte priority = 5,
> System.TimeSpan timeToLive = {00:00:05}, bool specifiedTimeToLive = true)
> Line 88 + 0x17 bytes	C#
> NMS.ActiveMQ.DLL!ActiveMQ.MessageProducer.Send(NMS.IDestination
> destination = {queue://test}, NMS.IMessage message =
> {ActiveMQBytesMessage[ ProducerId= Destination= TransactionId=
> OriginalDestination= MessageId=MessageId[ ProducerId=ProducerId[
> ConnectionId=a5e10bc2-46ce-4b5b-be85-d1ed6e0c7575 Value=1 SessionId=1 ]
> ProducerSequenceId=1 BrokerSequenceId=0 ] OriginalTransactionId= GroupID=
> GroupSequence=0 CorrelationId=0 Persistent=False Expiration=0 Priority=0
> ReplyTo=temp-queue://a5e10bc2-46ce-4b5b-be85-d1ed6e0c7575:1
> Timestamp=128310953802143903 Type= Content=System.Byte[]
> MarshalledProperties= DataStructure= TargetConsumerId= Compressed=False
> RedeliveryCounter=0 BrokerPath= Arrival=0 UserID= RecievedByDFBridge=False
> Droppable=False ]}, bool persistent = false, byte priority = 5,
> System.TimeSpan timeToLive = {00:00:05}) Line 62 + 0x27 bytes	C#
>  	
> When looking at the code for MessageProducer it looks like timestamp has
> been omitted from the calculation of expiration: 
> 
> if (specifiedTimeToLive) {
>   activeMessage.Expiration =
> ActiveMQ.Util.DateUtils.ToJavaTime(timeToLive);
> }
> 
> It looks like a bug to me, do you agree? 
> 
>    Peter
> 
> 

-- 
View this message in context: http://www.nabble.com/ActiveMQ.Net%3A-Client-does-not-include-timestamp-when-calculating-expiration-tf4312287s2354.html#a12412240
Sent from the ActiveMQ - Dev mailing list archive at Nabble.com.


Mime
View raw message