https://issues.apache.org/jira/browse/AMQNET-418
diff --git a/src/main/csharp/Transactions/RecoveryFileLogger.cs b/src/main/csharp/Transactions/RecoveryFileLogger.cs
index 7fc30e9..41296c3 100644
--- a/src/main/csharp/Transactions/RecoveryFileLogger.cs
+++ b/src/main/csharp/Transactions/RecoveryFileLogger.cs
@@ -30,16 +30,17 @@
{
public class RecoveryFileLogger : IRecoveryLogger
{
+ private readonly object syncRoot = new object();
+
private string location;
private bool autoCreateLocation;
private string resourceManagerId;
- private readonly object syncRoot = new object();
public RecoveryFileLogger()
{
// Set the path by default to the location of the executing assembly.
// May need to change this to current working directory, not sure.
- this.location = "";
+ this.location = string.Empty;
}
/// <summary>
@@ -59,14 +60,18 @@
{
get
{
- if(String.IsNullOrEmpty(this.location))
+ if(string.IsNullOrEmpty(this.location))
{
return Directory.GetCurrentDirectory();
}
return this.location;
}
- set { this.location = Uri.UnescapeDataString(value); }
+
+ set
+ {
+ this.location = Uri.UnescapeDataString(value);
+ }
}
/// <summary>
@@ -114,17 +119,17 @@
try
{
- lock (syncRoot)
- {
- RecoveryInformation info = new RecoveryInformation(xid, recoveryInformation);
- Tracer.Debug("Serializing Recovery Info to file: " + Filename);
+ string filename = this.CreateFilename(xid);
- IFormatter formatter = new BinaryFormatter();
- using (FileStream recoveryLog = new FileStream(Filename, FileMode.OpenOrCreate, FileAccess.Write))
- {
- formatter.Serialize(recoveryLog, info);
- }
+ RecoveryInformation info = new RecoveryInformation(xid, recoveryInformation);
+ Tracer.Debug("Serializing Recovery Info to file: " + filename);
+
+ IFormatter formatter = new BinaryFormatter();
+ using (FileStream recoveryLog = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write))
+ {
+ formatter.Serialize(recoveryLog, info);
}
+
}
catch (Exception ex)
{
@@ -135,52 +140,61 @@
public KeyValuePair<XATransactionId, byte[]>[] GetRecoverables()
{
- KeyValuePair<XATransactionId, byte[]>[] result = new KeyValuePair<XATransactionId, byte[]>[0];
- RecoveryInformation info = TryOpenRecoveryInfoFile();
+ IList<RecoveryInformation> infos = this.TryOpenRecoveryInfoFile();
- if (info != null)
+ KeyValuePair<XATransactionId, byte[]>[] results = new KeyValuePair<XATransactionId, byte[]>[infos.Count];
+
+ int index = 0;
+ foreach (RecoveryInformation info in infos)
{
- result = new KeyValuePair<XATransactionId, byte[]>[1];
- result[0] = new KeyValuePair<XATransactionId, byte[]>(info.Xid, info.TxRecoveryInfo);
+ results[index++] = new KeyValuePair<XATransactionId, byte[]>(info.Xid, info.TxRecoveryInfo);
}
- return result;
+ return results;
}
public void LogRecovered(XATransactionId xid)
{
- lock (syncRoot)
+ try
{
- try
- {
- Tracer.Debug("Attempting to remove stale Recovery Info file: " + Filename);
- File.Delete(Filename);
- }
- catch(Exception ex)
- {
- Tracer.Debug("Caught Exception while removing stale RecoveryInfo file: " + ex.Message);
- return;
- }
+ string filename = this.CreateFilename(xid);
+
+ Tracer.Debug("Attempting to remove stale Recovery Info file: " + filename);
+
+ File.Delete(filename);
+ }
+ catch (Exception ex)
+ {
+ Tracer.Debug("Caught Exception while removing stale RecoveryInfo file: " + ex.Message);
}
}
public void Purge()
{
- lock (syncRoot)
+ lock (this.syncRoot)
{
try
{
- Tracer.Debug("Attempting to remove stale Recovery Info file: " + Filename);
- File.Delete(Filename);
+ IEnumerable<string> files = this.GetFilesForResourceManagerId();
+
+ foreach (var file in files)
+ {
+ Tracer.Debug("Attempting to remove stale Recovery Info file: " + file);
+ File.Delete(file);
+ }
}
- catch(Exception ex)
+ catch (Exception ex)
{
Tracer.Debug("Caught Exception while removing stale RecoveryInfo file: " + ex.Message);
- return;
}
}
}
+ private IEnumerable<string> GetFilesForResourceManagerId()
+ {
+ return Directory.GetFiles(this.Location, this.ResourceManagerId + "_*.bin");
+ }
+
public string LoggerType
{
get { return "file"; }
@@ -188,9 +202,20 @@
#region Recovery File Opeations
- private string Filename
+ private string CreateFilename(XATransactionId xaTransactionId)
{
- get { return Location + Path.DirectorySeparatorChar + ResourceManagerId + ".bin"; }
+ return string.Format(
+ "{0}{1}{2}_{3}.bin",
+ this.Location,
+ Path.DirectorySeparatorChar,
+ this.ResourceManagerId,
+ GetHexValue(xaTransactionId));
+ }
+
+ private static string GetHexValue(XATransactionId xid)
+ {
+ string transactionIdHexValue = BitConverter.ToString(xid.GlobalTransactionId);
+ return transactionIdHexValue.Replace("-", string.Empty);
}
[Serializable]
@@ -234,33 +259,27 @@
}
}
- private RecoveryInformation TryOpenRecoveryInfoFile()
+ private IList<RecoveryInformation> TryOpenRecoveryInfoFile()
{
- RecoveryInformation result = null;
+ List<RecoveryInformation> result = new List<RecoveryInformation>();
- Tracer.Debug("Checking for Recoverable Transactions filename: " + Filename);
+ IEnumerable<string> files = this.GetFilesForResourceManagerId();
- lock (syncRoot)
+ foreach (var file in files)
{
+ Tracer.Debug("Checking for Recoverable Transactions filename: " + file);
try
{
- if (!File.Exists(Filename))
+ using (FileStream recoveryLog = new FileStream(file, FileMode.Open, FileAccess.Read))
{
- return null;
- }
-
- using(FileStream recoveryLog = new FileStream(Filename, FileMode.Open, FileAccess.Read))
- {
- Tracer.Debug("Found Recovery Log File: " + Filename);
+ Tracer.Debug("Found Recovery Log File: " + file);
IFormatter formatter = new BinaryFormatter();
- result = formatter.Deserialize(recoveryLog) as RecoveryInformation;
+ result.Add(formatter.Deserialize(recoveryLog) as RecoveryInformation);
}
}
- catch(Exception ex)
+ catch (Exception ex)
{
- Tracer.ErrorFormat("Error while opening Recovery file {0} error message: {1}", Filename, ex.Message);
- // Nothing to restore.
- return null;
+ Tracer.ErrorFormat("Error while opening Recovery file {0} error message: {1}", file, ex.Message);
}
}
diff --git a/src/test/csharp/Transactions/RecoveryFileLoggerTest.cs b/src/test/csharp/Transactions/RecoveryFileLoggerTest.cs
index cb1abd8..b82bb25 100644
--- a/src/test/csharp/Transactions/RecoveryFileLoggerTest.cs
+++ b/src/test/csharp/Transactions/RecoveryFileLoggerTest.cs
@@ -26,10 +26,12 @@
namespace Apache.NMS.ActiveMQ.Test.Transactions
{
+ using System.Threading;
+
[TestFixture]
public class RecoveryFileLoggerTest
{
- private string rmId;
+ private string resourceManagerId;
private string nonExistantPath;
private string autoCreatePath;
private string nonDefaultLogLocation;
@@ -37,7 +39,7 @@
[SetUp]
public void SetUp()
{
- this.rmId = Guid.NewGuid().ToString();
+ this.resourceManagerId = Guid.NewGuid().ToString();
this.nonExistantPath = Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString());
this.nonDefaultLogLocation = Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString());
this.autoCreatePath = Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString());
@@ -48,12 +50,8 @@
[TearDown]
public void TearDown()
{
- if(Directory.Exists(autoCreatePath))
- {
- Directory.Delete(autoCreatePath);
- }
-
- Directory.Delete(nonDefaultLogLocation, true);
+ SafeDeleteDirectory(autoCreatePath, 1000);
+ SafeDeleteDirectory(nonDefaultLogLocation, 1000);
}
[Test]
@@ -61,7 +59,7 @@
{
RecoveryFileLogger logger = new RecoveryFileLogger();
- logger.Initialize(rmId);
+ logger.Initialize(this.resourceManagerId);
Assert.AreEqual(Directory.GetCurrentDirectory(), logger.Location);
}
@@ -72,7 +70,7 @@
RecoveryFileLogger logger = new RecoveryFileLogger();
logger.Location = nonDefaultLogLocation;
- logger.Initialize(rmId);
+ logger.Initialize(this.resourceManagerId);
Assert.AreEqual(nonDefaultLogLocation, logger.Location);
}
@@ -86,7 +84,7 @@
logger.AutoCreateLocation = true;
logger.Location = autoCreatePath;
- logger.Initialize(rmId);
+ logger.Initialize(this.resourceManagerId);
Assert.IsTrue(Directory.Exists(autoCreatePath));
Assert.AreEqual(autoCreatePath, logger.Location);
@@ -102,7 +100,7 @@
try
{
- logger.Initialize(rmId);
+ logger.Initialize(this.resourceManagerId);
Assert.Fail("Should have detected an invalid dir and thrown an exception");
}
catch
@@ -116,7 +114,7 @@
RecoveryFileLogger logger = new RecoveryFileLogger();
logger.Location = nonDefaultLogLocation;
- logger.Initialize(rmId);
+ logger.Initialize(this.resourceManagerId);
Assert.IsTrue(logger.GetRecoverables().Length == 0);
}
@@ -126,27 +124,14 @@
{
RecoveryFileLogger logger = new RecoveryFileLogger();
- byte[] globalId = new byte[32];
- byte[] branchQ = new byte[32];
- byte[] recoveryData = new byte[256];
-
- Random gen = new Random();
-
- gen.NextBytes(globalId);
- gen.NextBytes(branchQ);
- gen.NextBytes(recoveryData);
-
logger.Location = nonDefaultLogLocation;
- logger.Initialize(rmId);
+ logger.Initialize(this.resourceManagerId);
- XATransactionId xid = new XATransactionId();
- xid.GlobalTransactionId = globalId;
- xid.BranchQualifier = branchQ;
+ TransactionData transactionData = new TransactionData();
+ logger.LogRecoveryInfo(transactionData.Transaction, transactionData.RecoveryData);
- logger.LogRecoveryInfo(xid, recoveryData);
-
- Assert.IsTrue(File.Exists(Path.Combine(logger.Location, rmId + ".bin")),
- "Recovery File was not created");
+ Assert.IsTrue(File.Exists(this.GetFilename(logger, transactionData)),
+ "Recovery File was not created");
}
[Test]
@@ -154,36 +139,58 @@
{
RecoveryFileLogger logger = new RecoveryFileLogger();
- byte[] globalId = new byte[32];
- byte[] branchQ = new byte[32];
- byte[] recoveryData = new byte[256];
+ logger.Location = this.nonDefaultLogLocation;
+ logger.Initialize(this.resourceManagerId);
- Random gen = new Random();
+ TransactionData transactionData01 = new TransactionData();
+ logger.LogRecoveryInfo(transactionData01.Transaction, transactionData01.RecoveryData);
+ TransactionData transactionData02 = new TransactionData();
+ logger.LogRecoveryInfo(transactionData02.Transaction, transactionData02.RecoveryData);
- gen.NextBytes(globalId);
- gen.NextBytes(branchQ);
- gen.NextBytes(recoveryData);
-
- logger.Location = nonDefaultLogLocation;
- logger.Initialize(rmId);
-
- XATransactionId xid = new XATransactionId();
- xid.GlobalTransactionId = globalId;
- xid.BranchQualifier = branchQ;
-
- logger.LogRecoveryInfo(xid, recoveryData);
-
- Assert.IsTrue(File.Exists(Path.Combine(logger.Location, rmId + ".bin")),
- "Recovery File was not created");
- Assert.IsTrue(logger.GetRecoverables().Length == 1,
- "Did not recover the logged record.");
+ Assert.IsTrue(File.Exists(this.GetFilename(logger, transactionData01)), "Recovery File was not created");
+ Assert.IsTrue(File.Exists(this.GetFilename(logger, transactionData02)), "Recovery File was not created");
+ Assert.AreEqual(2, logger.GetRecoverables().Length, "Did not recover the logged record.");
KeyValuePair<XATransactionId, byte[]>[] records = logger.GetRecoverables();
- Assert.AreEqual(1, records.Length);
+ Assert.AreEqual(2, records.Length);
- Assert.AreEqual(globalId, records[0].Key.GlobalTransactionId, "Incorrect Global TX Id returned");
- Assert.AreEqual(branchQ, records[0].Key.BranchQualifier, "Incorrect Branch Qualifier returned");
- Assert.AreEqual(recoveryData, records[0].Value, "Incorrect Recovery Information returned");
+ foreach (var keyValuePair in records)
+ {
+ if (BitConverter.ToString(keyValuePair.Key.GlobalTransactionId) == BitConverter.ToString(transactionData01.Transaction.GlobalTransactionId))
+ {
+ Assert.AreEqual(transactionData01.GlobalId, keyValuePair.Key.GlobalTransactionId, "Incorrect Global TX Id returned");
+ Assert.AreEqual(transactionData01.BranchQ, keyValuePair.Key.BranchQualifier, "Incorrect Branch Qualifier returned");
+ Assert.AreEqual(transactionData01.RecoveryData, keyValuePair.Value, "Incorrect Recovery Information returned");
+ }
+ else if (BitConverter.ToString(keyValuePair.Key.GlobalTransactionId) == BitConverter.ToString(transactionData02.Transaction.GlobalTransactionId))
+ {
+ Assert.AreEqual(transactionData02.GlobalId, keyValuePair.Key.GlobalTransactionId, "Incorrect Global TX Id returned");
+ Assert.AreEqual(transactionData02.BranchQ, keyValuePair.Key.BranchQualifier, "Incorrect Branch Qualifier returned");
+ Assert.AreEqual(transactionData02.RecoveryData, keyValuePair.Value, "Incorrect Recovery Information returned");
+ }
+ else
+ {
+ Assert.Fail("Transaction not found.");
+ }
+ }
+ }
+
+ [Test]
+ public void TestLogRecovered()
+ {
+ RecoveryFileLogger logger = new RecoveryFileLogger();
+
+ logger.Location = nonDefaultLogLocation;
+ logger.Initialize(this.resourceManagerId);
+
+ TransactionData transactionData = new TransactionData();
+ logger.LogRecoveryInfo(transactionData.Transaction, transactionData.RecoveryData);
+
+ Assert.IsTrue(File.Exists(this.GetFilename(logger, transactionData)), "Recovery File was not created");
+
+ logger.LogRecovered(transactionData.Transaction);
+
+ this.AssertFileIsDeleted(this.GetFilename(logger, transactionData), 1000);
}
[Test]
@@ -191,34 +198,116 @@
{
RecoveryFileLogger logger = new RecoveryFileLogger();
- byte[] globalId = new byte[32];
- byte[] branchQ = new byte[32];
- byte[] recoveryData = new byte[256];
-
- Random gen = new Random();
-
- gen.NextBytes(globalId);
- gen.NextBytes(branchQ);
- gen.NextBytes(recoveryData);
-
logger.Location = nonDefaultLogLocation;
- logger.Initialize(rmId);
+ logger.Initialize(this.resourceManagerId.ToString());
- XATransactionId xid = new XATransactionId();
- xid.GlobalTransactionId = globalId;
- xid.BranchQualifier = branchQ;
+ TransactionData transactionData01 = new TransactionData();
+ logger.LogRecoveryInfo(transactionData01.Transaction, transactionData01.RecoveryData);
+ TransactionData transactionData02 = new TransactionData();
+ logger.LogRecoveryInfo(transactionData02.Transaction, transactionData02.RecoveryData);
- logger.LogRecoveryInfo(xid, recoveryData);
-
- Assert.IsTrue(File.Exists(Path.Combine(logger.Location, rmId + ".bin")),
- "Recovery File was not created");
+ Assert.IsTrue(File.Exists(this.GetFilename(logger, transactionData01)), "Recovery File was not created");
+ Assert.IsTrue(File.Exists(this.GetFilename(logger, transactionData02)), "Recovery File was not created");
logger.Purge();
- Assert.IsFalse(File.Exists(Path.Combine(logger.Location, rmId + ".bin")),
- "Recovery File was not created");
+ this.AssertFileIsDeleted(this.GetFilename(logger, transactionData01), 1000);
+ this.AssertFileIsDeleted(this.GetFilename(logger, transactionData02), 1000);
}
+ private string GetFilename(RecoveryFileLogger logger, TransactionData transactionData)
+ {
+ return string.Format(
+ "{0}{1}{2}_{3}.bin",
+ logger.Location,
+ Path.DirectorySeparatorChar,
+ this.resourceManagerId.ToString(),
+ BitConverter.ToString(transactionData.Transaction.GlobalTransactionId).Replace("-", string.Empty));
+ }
+
+ private void AssertFileIsDeleted(string filename, int timeout)
+ {
+ var expiration = DateTime.Now.Add(TimeSpan.FromMilliseconds(timeout));
+ while (File.Exists(filename))
+ {
+ if (expiration < DateTime.Now)
+ {
+ Assert.Fail("Recovery File was not removed");
+ }
+
+ Thread.Sleep(5);
+ }
+ }
+
+ private void SafeDeleteDirectory(string directory, int timeout)
+ {
+ var expiration = DateTime.Now.Add(TimeSpan.FromMilliseconds(timeout));
+ while (true)
+ {
+ if (!Directory.Exists(directory))
+ {
+ return;
+ }
+
+ try
+ {
+ Directory.Delete(directory, true);
+ return;
+ }
+ catch (Exception)
+ {
+ }
+
+ if (expiration < DateTime.Now)
+ {
+ return;
+ }
+
+ Thread.Sleep(5);
+ }
+ }
+
+ private class TransactionData
+ {
+ private static readonly Random Random = new Random();
+
+ private readonly XATransactionId xid;
+
+ private readonly byte[] recoveryData = new byte[256];
+ private readonly byte[] globalId = new byte[32];
+ private readonly byte[] branchQ = new byte[32];
+
+ public TransactionData()
+ {
+ Random.NextBytes(this.globalId);
+ Random.NextBytes(this.branchQ);
+ Random.NextBytes(this.recoveryData);
+
+ this.xid = new XATransactionId();
+ this.xid.GlobalTransactionId = this.globalId;
+ this.xid.BranchQualifier = this.branchQ;
+ }
+
+ public XATransactionId Transaction
+ {
+ get { return this.xid; }
+ }
+
+ public byte[] RecoveryData
+ {
+ get { return this.recoveryData; }
+ }
+
+ public byte[] GlobalId
+ {
+ get { return this.globalId; }
+ }
+
+ public byte[] BranchQ
+ {
+ get { return this.branchQ; }
+ }
+ }
}
}