Lucene.NET Lock obtain timed out error - c#

I'm getting the following error when running a process that updates a Lucene index with some User data (a domain object).
Lucene.Net.Store.LockObtainFailedException: Lock obtain timed out:
AzureLock#write.lock. at Lucene.Net.Store.Lock.Obtain(Int64
lockWaitTimeout) in
d:\Lucene.Net\FullRepo\trunk\src\core\Store\Lock.cs: line 97 at
Lucene.Net.Index.IndexWriter.Init(Directory d, Analyzer a, Boolean
create, IndexDeletionPolicy deletionPolicy, Int32 maxFieldLength,
IndexingChain indexingChain, IndexCommit commit) in
d:\Lucene.Net\FullRepo\trunk\src\core\Index\IndexWriter.cs: line 1228
at Lucene.Net.Index.IndexWriter..ctor(Directory d, Analyzer a,
MaxFieldLength mfl) in
d:\Lucene.Net\FullRepo\trunk\src\core\Index\IndexWriter.cs: line 174
at
MyApp.ApplicationServices.Search.Users.UsersSearchEngineService.EnsureIndexWriter()
at
MyApp.ApplicationServices.Search.Users.UsersSearchEngineService.DoWriterAction(Action`1
action) at....
I've inherited the following code and my knowledge of Lucene is not great, so any pointers would be appreciated.
public class UsersSearchEngineService : IUsersSearchEngineService
{
private readonly Directory directory;
private readonly Analyzer analyzer;
private static IndexWriter indexWriter;
private static readonly Object WriterLock = new Object();
private bool disposed;
public UsersSearchEngineService (ILuceneDirectoryProvider directoryProvider)
{
directory = directoryProvider.GetDirectory();
analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
}
public int UpdateIndex(IEnumerable<User> itemsToIndex)
{
var updatedCount = 0;
foreach (var itemToIndex in itemsToIndex)
{
try
{
ExecuteRemoveItem(itemToIndex.UserId);
var document = CreateIndexDocument(itemToIndex);
DoWriterAction(writer => writer.AddDocument(document));
updatedCount++;
}
catch (Exception ex)
{
EventLogProvider.Error("Error updating index for User with id:[{0}]", ex, itemToIndex.UserId);
}
}
DoWriterAction(writer =>
{
writer.Commit();
writer.Optimize();
});
return updatedCount;
}
private static Document CreateIndexDocument(User itemToIndex)
{
var document = new Document();
document.Add(new Field("id", itemToIndex.UserId.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NOT_ANALYZED));
//...
//omitted other fields being added here
//...
return document;
}
void ExecuteRemoveItem(int entryId)
{
var searchQuery = GetIdSearchQuery(entryId);
DoWriterAction(writer => writer.DeleteDocuments(searchQuery));
}
void DoWriterAction(Action<IndexWriter> action)
{
lock (WriterLock)
{
EnsureIndexWriter();
}
action(indexWriter);
}
void EnsureIndexWriter()
{
if (indexWriter != null)
{
return;
}
indexWriter = new IndexWriter(this.directory, this.analyzer, IndexWriter.MaxFieldLength.UNLIMITED);
indexWriter.SetMergePolicy(new LogDocMergePolicy(indexWriter) { MergeFactor = 5 });
var retryStrategy = new ExponentialBackoff(5, TimeSpan.FromMilliseconds(200), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10));
var retryPolicy = new RetryPolicy<LuceneWriterLockedErrorDetectionStrategy>(retryStrategy);
retryPolicy.Retrying += (sender, args) => EventLogProvider.Warn("Retrying lock delete Attempt: " + args.CurrentRetryCount);
if (IndexWriter.IsLocked(this.directory))
{
retryPolicy.ExecuteAction(() =>
{
EventLogProvider.Info("Something left a lock in the index folder: Attempting to it");
IndexWriter.Unlock(directory);
EventLogProvider.Info("Lock Deleted... can proceed");
});
}
}
~UsersSearchEngineService ()
{
Dispose();
}
public void Dispose()
{
lock (WriterLock)
{
if (!disposed)
{
var writer = indexWriter;
if (writer != null)
{
try
{
writer.Dispose();
}
catch (ObjectDisposedException e)
{
EventLogProvider.Error("Exception while disposing SearchEngineService", e);
}
indexWriter = null;
}
var disposeDirectory = directory;
if (disposeDirectory != null)
{
try
{
disposeDirectory.Dispose();
}
catch (ObjectDisposedException e)
{
EventLogProvider.Error("Exception while disposing SearchEngineService", e);
}
}
disposed = true;
}
}
GC.SuppressFinalize(this);
}
}
In case it's relevant:
the application is hosted as an Azure Web App, running 2 instances
the index is stored in Azure Storage
the procedure runs as a scheduled process defined in a CMS.
The CMS will only launch allow one instance of scheduled process to be running at any
one time in a load balanced scenario (e.g. when running 2
instances in Azure)
Does the EnsureIndexWriter method look correct? If not, how should it be reworked?

Related

Better way to serialize with a timeout in c#.Net

My use case:
In a single threaded application, I need to serialize arbitrary classes for logging purposes.
The arbitrary classes are predominantly translated in an automated way from a massive VB6 application into .NET.
If serialized without a timeout, the serialization method will loop until it runs out of memory.
This is what I have currently:
internal class Serializer
{
private readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public volatile string result = null;
public volatile Func<string> toExecute = null;
public Thread thread;
public ManualResetEventSlim messageToSender = new ManualResetEventSlim(false);
public ManualResetEventSlim messageToReceiver = new ManualResetEventSlim(false);
public Serializer()
{
thread = new Thread(new ThreadStart(run));
thread.Start();
}
~Serializer()
{
try
{
if (messageToSender != null) messageToSender.Dispose();
}
catch { };
try
{
if (messageToReceiver != null) messageToReceiver.Dispose();
}
catch { };
}
public volatile bool ending = false;
public void run()
{
while (!ending)
{
try
{
if (toExecute != null)
{
result = toExecute();
}
messageToReceiver.Reset();
messageToSender.Set();
messageToReceiver.Wait();
}
catch (ThreadInterruptedException)
{
log.Warn("Serialization interrupted");
break;
}
catch (ThreadAbortException)
{
Thread.ResetAbort();
result = null;
}
catch (Exception ex)
{
log.Error("Error in Serialization", ex);
Console.WriteLine(ex);
break;
}
}
}
}
public class LocalStructuredLogging
{
private static volatile Serializer _serializer;
private static Serializer serializer
{
get
{
if (_serializer == null)
{
_serializer = new Serializer();
}
return _serializer;
}
}
public void LogStucturedEnd()
{
try
{
if (serializer != null)
{
serializer.ending = true;
serializer.thread.Interrupt();
}
}
catch { }
}
internal ConcurrentDictionary<long, bool> disallowedToSerialize = new ConcurrentDictionary<long, bool>();
public string TrySerialize<T>(T payload, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
bool dummy;
unchecked
{
if (disallowedToSerialize.TryGetValue(hashEl, out dummy))
{
return "°,°";
}
}
serializer.toExecute = () =>
{
try
{
return Newtonsoft.Json.JsonConvert.SerializeObject(payload, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°°°";
}
};
try
{
serializer.messageToSender.Reset();
serializer.messageToReceiver.Set();
if (serializer.messageToSender.Wait(6000))
{
return Interlocked.Exchange(ref serializer.result, null);
}
serializer.toExecute = null;
serializer.thread.Abort();
serializer.messageToSender.Wait(2000);
disallowedToSerialize.TryAdd(hashEl, false);
return "°§°";
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°-°";
}
}
}
The code is called as in the following (test is an arbitrary class instance):
var logger = new LocalStructuredLogging();
var rr5 = logger.TrySerialize(test);
Although it seems to do the job, there are some issues with it:
it has a dependency on Thread.Abort
it is time dependent, so it will thus produce varied results on a loaded system
every class instance is treated like every other class instance - no tweaking
...
So, are there any better solutions available ?
Based upon dbc's excellent answer, I managed to create a better timed serializer.
It resolves all 3 issues mentioned above:
public class TimedJsonTextWriter : JsonTextWriter
{
public int? MaxDepth { get; set; }
public TimeSpan? MaxTimeUsed { get; set; }
public int MaxObservedDepth { get; private set; }
private DateTime start = DateTime.Now;
public TimedJsonTextWriter(TextWriter writer, JsonSerializerSettings settings, TimeSpan? maxTimeUsed)
: base(writer)
{
this.MaxDepth = (settings == null ? null : settings.MaxDepth);
this.MaxObservedDepth = 0;
this.MaxTimeUsed = maxTimeUsed;
}
public TimedJsonTextWriter(TextWriter writer, TimeSpan? maxTimeUsed, int? maxDepth = null)
: base(writer)
{
this.MaxDepth = maxDepth;
this.MaxTimeUsed = maxTimeUsed;
}
public override void WriteStartArray()
{
base.WriteStartArray();
CheckDepth();
}
public override void WriteStartConstructor(string name)
{
base.WriteStartConstructor(name);
CheckDepth();
}
public override void WriteStartObject()
{
base.WriteStartObject();
CheckDepth();
}
uint checkDepthCounter = 0;
private void CheckDepth()
{
MaxObservedDepth = Math.Max(MaxObservedDepth, Top);
if (Top > MaxDepth)
throw new JsonSerializationException($"Depth {Top} Exceeds MaxDepth {MaxDepth} at path \"{Path}\"");
unchecked
{
if ((++checkDepthCounter & 0x3ff) == 0 && DateTime.Now - start > MaxTimeUsed)
throw new JsonSerializationException($"Time Usage Exceeded at path \"{Path}\"");
}
}
}
public class LocalStructuredLogging
{
public void LogStucturedEnd()
{
}
internal HashSet<long> disallowedToSerialize = new HashSet<long>();
public string TrySerialize<T>(T payload, int maxDepth = 100, int secondsToTimeout = 2, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
if (disallowedToSerialize.Contains(hashEl))
{
return "°,°";
}
try
{
var settings = new JsonSerializerSettings { MaxDepth = maxDepth, ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore };
using (var writer = new StringWriter())
{
using (var jsonWriter = new TimedJsonTextWriter(writer, settings, new TimeSpan(0, 0, secondsToTimeout)))
{
JsonSerializer.Create(settings).Serialize(jsonWriter, payload);
// Log the MaxObservedDepth here, if you want to.
}
return writer.ToString();
}
}
catch (Exception)
{
disallowedToSerialize.Add(hashEl);
return "°-°";
}
}
}
The only issue remaining are the Hash collisions, which are easy to solve (e.g. by using the source file name as well or use another type of Collection).
The correct way to run an action timed would be to do something like the following. I would recommend taking a second look at how serialization should work as well :).
/// <summary>
/// Run an action timed.
/// </summary>
/// <param name="action">Action to execute timed.</param>
/// <param name="secondsTimout">Seconds before Task should cancel.</param>
/// <returns></returns>
public static async Task RunTimeout(Action action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
await Task.Run(action, tokenSource.Token);
}
You may also want to return a variable upon the completion of your timed task. That can be done like so...
public static async Task<T> RunTimeout<T>(Func<T> action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
var result = await Task.Run(action, tokenSource.Token);
return result;
}

How to write repository unit tests with respect to Cosmos database(SQLApi + CosmosClient)

I have a class as below :
public class CosmosRepository: ICosmosRepository
{
private ICosmoDBSettings cosmoDbSettings;
private CosmosClient cosmosClient;
private static readonly object syncLock = new object();
// The database we will create
private Database database;
public CosmosRepository(ICosmoDBSettings cosmoDBSettings)
{
this.cosmoDbSettings = cosmoDBSettings;
this.InitializeComponents();
}
private void InitializeComponents()
{
try
{
if (cosmosClient != null)
{
return;
}
lock (syncLock)
{
if (cosmosClient != null)
{
return;
}
this.cosmosClient = new CosmosClient(
cosmoDbSettings.CosmosDbUri
, cosmoDbSettings.CosmosDbAuthKey
, new CosmosClientOptions
{
ConnectionMode = ConnectionMode.Direct
}
);
this.database = this.cosmosClient.CreateDatabaseIfNotExistsAsync(cosmoDbSettings.DocumentDbDataBaseName).Result;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
I have my repository method as:
Don't bother about hardcoded values.
public async Task<Employee> GetById()
{
var container = this.database.GetContainer("Employees");
var document = await container.ReadItemAsync<Employee>("44A85B9E-2522-4BDB-891A-
9EA91F6D4CBF", new PartitionKey("PartitionKeyValue"));
return document.Response;
}
Note
How to write Unit Test(MS Unit tests) in .NET Core with respect to Cosmos Database?
How to mock CosmosClient and all its methods.
Could someone help me with this issue?
My UnitTests looks like:
public class UnitTest1
{
private Mock<ICosmoDBSettings> cosmoDbSettings;
private Mock<CosmosClient> cosmosClient;
private Mock<Database> database;
[TestMethod]
public async Task TestMethod()
{
this.CreateSubject();
var databaseResponse = new Mock<DatabaseResponse>();
var helper = new CosmosDBHelper(this.cosmoDbSettings.Object);
this.cosmosClient.Setup(d => d.CreateDatabaseIfNotExistsAsync("TestDatabase", It.IsAny<int>(), It.IsAny<RequestOptions>(), It.IsAny<CancellationToken>())).ReturnsAsync(databaseResponse.Object);
var mockContainer = new Mock<Container>();
this.database.Setup(x => x.GetContainer(It.IsAny<string>())).Returns(mockContainer.Object);
var mockItemResponse = new Mock<ItemResponse<PortalAccount>>();
mockItemResponse.Setup(x => x.StatusCode).Returns(It.IsAny<HttpStatusCode>);
var mockPortalAccount = new PortalAccount { PortalAccountGuid = Guid.NewGuid() };
mockItemResponse.Setup(x => x.Resource).Returns(mockPortalAccount);
mockContainer.Setup(c => c.ReadItemAsync<PortalAccount>(It.IsAny<string>(),It.IsAny<PartitionKey>(), It.IsAny<ItemRequestOptions>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockItemResponse.Object);
var pas = helper.GetById().Result;
Assert.AreEqual(pas.PortalAccountGuid, mockPortalAccount.PortalAccountGuid);
}
public void CreateSubject(ICosmoDBSettings cosmoDBSettings = null)
{
this.cosmoDbSettings = cosmoDbSettings ?? new Mock<ICosmoDBSettings>();
this.cosmoDbSettings.Setup(x => x.CosmosDbUri).Returns("https://localhost:8085/");
this.cosmoDbSettings.Setup(x => x.CosmosDbAuthKey).Returns("C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==");
this.cosmoDbSettings.Setup(x => x.DocumentDbDataBaseName).Returns("TestDatabase");
this.database = new Mock<Database>();
this.cosmosClient = new Mock<CosmosClient>();
}
}
Note:
Exception:
Response status code does not indicate success: 404 Substatus: 0 Reason: (Microsoft.Azure.Documents.DocumentClientException: Message: {"Errors":["Resource Not Found"]}
I'm not creating a document. directly I'm fetching the document because I'm returning the mock response only. Is it correct??

C# detect offline mode (SQL Server connection)

I am developing a C# application which connects to SQL Server. If the network connection breaks, the application should be able to go into a "read-only mode" (offline mode) and only read data from a local database. Right now, I am trying to figure out how to detect the disconnect:
public int executeNonQuery(string query, List<SqlParameter> parameters)
{
int result;
using (SqlConnection sqlConnection = new SqlConnection(ConnectionString))
{
tryOpenSqlConnection(sqlConnection);
using (SqlCommand cmd = new SqlCommand(query, sqlConnection))
{
if (parameters != null)
{
cmd.Parameters.AddRange(parameters.ToArray());
}
result = cmd.ExecuteNonQuery();
}
sqlConnection.Close();
}
return result;
}
private void tryOpenSqlConnection(SqlConnection sqlConnection)
{
try
{
sqlConnection.Open();
}
catch (SqlException se)
{
if (se.Number == 26)
{
catchOfflineMode(se);
}
throw se;
}
}
//...
private void catchOfflineMode(SqlException se)
{
Console.WriteLine("SqlException: " + se.Message);
Console.WriteLine("Setting offline mode...");
//...
}
I thought about using the SQL error codes to detect the loss of connection. But the problem is that sometimes I get exceptions only after the SqlConnection already established, e.g. during execution of the command. The last exception I got was
Error Code 121 - The semaphore timeout period has expired
So, I would have to check every single error code that could have to do with losing network connection.
EDIT: I also thought about catching every SqlException and then checking the ethernet connection (e.g. pinging the server) to check whether the exception comes from a lost connection or not.
Are there better ways to do it?
I came up with my own solution by creating a simple helper class called
ExternalServiceHandler.cs
which is used as a proxy for external service calls to detect the online and offline status of the application after an operation failed.
using System;
using System.Threading;
using System.Threading.Tasks;
using Application.Utilities;
namespace Application.ExternalServices
{
class ExternalServiceHandler: IExternalServiceHandler
{
public event EventHandler OnlineModeDetected;
public event EventHandler OfflineModeDetected;
private static readonly int RUN_ONLINE_DETECTION_SEC = 10;
private static ExternalServiceHandler instance;
private Task checkOnlineStatusTask;
private CancellationTokenSource cancelSource;
private Exception errorNoConnection;
public static ExternalServiceHandler Instance
{
get
{
if (instance == null)
{
instance = new ExternalServiceHandler();
}
return instance;
}
}
private ExternalServiceHandler()
{
errorNoConnection = new Exception("Could not connect to the server.");
}
public virtual void Execute(Action func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
func();
}
catch
{
if(offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
public virtual T Execute<T>(Func<T> func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
return func();
}
catch
{
if (offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
public virtual async Task ExecuteAsync(Func<Task> func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
await func();
}
catch
{
if (offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
public virtual async Task<T> ExecuteAsync<T>(Func<Task<T>> func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
return await func();
}
catch
{
if (offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
private bool offlineModeDetected()
{
bool isOffline = false;
if (!LocalMachine.isOnline())
{
isOffline = true;
Console.WriteLine("-- Offline mode detected (readonly). --");
// notify all modues that we're in offline mode
OnOfflineModeDetected(new EventArgs());
// start online detection task
cancelSource = new CancellationTokenSource();
checkOnlineStatusTask = Run(detectOnlineMode,
new TimeSpan(0,0, RUN_ONLINE_DETECTION_SEC),
cancelSource.Token);
}
return isOffline;
}
private void detectOnlineMode()
{
if(LocalMachine.isOnline())
{
Console.WriteLine("-- Online mode detected (read and write). --");
// notify all modules that we're online
OnOnlineModeDetected(new EventArgs());
// stop online detection task
cancelSource.Cancel();
}
}
public static async Task Run(Action action, TimeSpan period, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(period, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
action();
}
}
}
protected virtual void OnOfflineModeDetected(EventArgs e)
{
OfflineModeDetected?.Invoke(this, e);
}
protected virtual void OnOnlineModeDetected(EventArgs e)
{
OnlineModeDetected?.Invoke(this, e);
}
}
}
The LocalMachine.isOnline() method looks like this:
namespace Application.Utilities
{
public class LocalMachine
{
// ... //
public static bool isOnline()
{
try
{
using (var client = new WebClient())
{
string serveraddress = AppSettings.GetServerHttpAddress();
using (var stream = client.OpenRead(serveraddress))
{
return true;
}
}
}
catch
{
return false;
}
}
// ... //
}
The helper class can be used every time an external service call is made. In the following example, a SQL non query is executed by the ExternalServiceHandler:
public async Task<int> executeNonQueryAsync(string query)
{
return await ExternalServiceHandler.Instance.ExecuteAsync(async () =>
{
return await DBManager.executeNonQueryAsync(query);
});
}
The solution works fine for me. If you have any better ideas, please let me know.

IIS Remote Exception

I have an IIS Application which references a plugin manager which reads all available plugins from a folder, when it is hosted after about 5-10 minutes I start getting the following Exception
[RemotingException: Object '/1608465e_9d80_4b40_be20_4c96904643e0/wizi+0g5od5gwmunm_indiws_253.rem' has been disconnected or does not exist at the server.]
System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) +14416170
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) +388
PluginManager.Core.Interfaces.IPluggin.get_Id() +0
PluginManager.PluginDetails.get_Id() +27
I did some research and came accross ILease and ISponsor but I have no Idea how to implement it or how it works. My current code as it is at this moment, I just removed parts the body of the methods for clarity
[Edit : added the method bodies]
public class AssemblyReflectionProxy : MarshalByRefObject
{
private string _assemblyPath;
public AssemblyReflectionProxy()
{
Id = "";
}
public void LoadAssembly(String assemblyPath)
{
try
{
_assemblyPath = assemblyPath;
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
}
catch (FileNotFoundException)
{
}
}
public TResult Reflect<TResult>(Func<Assembly, TResult> func)
{
var directory = new FileInfo(_assemblyPath).Directory;
ResolveEventHandler resolveEventHandler = (s, e) => OnReflectionOnlyResolve(e, directory);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;
var assembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(a => String.Compare(a.Location, _assemblyPath, StringComparison.Ordinal) == 0);
var result = func(assembly);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
return result;
}
public T GetEntryType<T>()
{
var directory = new FileInfo(_assemblyPath).Directory;
ResolveEventHandler resolveEventHandler = (s, e) => OnReflectionOnlyResolve(e, directory);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;
var assembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(a => string.Compare(a.Location, _assemblyPath, StringComparison.Ordinal) == 0);
if (assembly != null)
{
var result = assembly.GetTypes();
var type = result.FirstOrDefault(x => x.GetInterface(typeof(T).Name) != null);
if (type != null)
{
var remoteObject = AppDomain.CurrentDomain.CreateInstanceFrom(type.Assembly.Location, type.FullName);
var obj = remoteObject.Unwrap();
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
return (T)obj;
}
}
return default(T);
}
private Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
{
var loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}
var assemblyName = new AssemblyName(args.Name);
var dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename))
{
return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
}
return Assembly.ReflectionOnlyLoad(args.Name);
}
private string Id { get; set; }
internal string GetId()
{
if (String.IsNullOrEmpty(Id))
{
var fileBytes = File.ReadAllBytes(_assemblyPath);
var hash = Convert.ToBase64String(fileBytes).GetHashCode();
var bytes = BitConverter.GetBytes(hash);
StringBuilder sb = new StringBuilder();
foreach (byte b in bytes)
sb.Append(b.ToString("X2"));
Id = sb.ToString();
}
return Id;
}
}
public sealed class AssemblyManager : MarshalByRefObject, IDisposable
{
private readonly Dictionary<string, AppDomain> _assemblyDomains = new Dictionary<string, AppDomain>();
readonly Dictionary<string, AssemblyReflectionProxy> _proxies = new Dictionary<string, AssemblyReflectionProxy>();
public AssemblyManager()
{
}
public string LoadAssembly(string assemblyPath)
{
var fileInfo = new FileInfo(assemblyPath);
var name = fileInfo.Name.Replace(".dll", "");
if (fileInfo.Exists)
{
if (!_assemblyDomains.ContainsKey(name))
{
var appDomain = CreateChildDomain(AppDomain.CurrentDomain, fileInfo.Name);
_assemblyDomains[name] = appDomain;
try
{
Type proxyType = typeof(AssemblyReflectionProxy);
{
var proxy = (AssemblyReflectionProxy)appDomain.CreateInstanceFrom(proxyType.Assembly.Location, proxyType.FullName).Unwrap();
proxy.LoadAssembly(assemblyPath);
_proxies[name] = proxy;
return name;
}
}
catch
{ }
}
else
{
return name;
}
}
return "";
}
public void Unload()
{
}
private AppDomain CreateChildDomain(AppDomain parentDomain, string domainName)
{
var evidence = new Evidence(parentDomain.Evidence);
var setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain(domainName, evidence, setup);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~AssemblyManager()
{
Dispose(false);
}
public IPluggin GetEntryPluggin(string name)
{
IPluggin plugin = default(IPluggin);
if (_proxies.ContainsKey(name))
{
plugin = _proxies[name].GetEntryType<IPluggin>();
}
return plugin;
}
private void Dispose(bool disposing)
{
if (disposing)
{
foreach (var appDomain in _assemblyDomains.Values)
AppDomain.Unload(appDomain);
_assemblyDomains.Clear();
}
}
internal string GetEntryPlugginID(string name)
{
string Id = "";
if (_proxies.ContainsKey(name))
{
Id = _proxies[name].GetId();
}
return Id;
}
}
My Interface is
public interface IPluggin
{
string Name { get; }
string Version { get; }
string Id { get; }
void Initialize();
string[] GetElements();
void SaveSettings(string settings);
void SetBasePath(string path);
}
.NET remoting is kind of deprecated, but I don't see why it would be a problem for communicating between application domains, so...
When you create a new instance of a remoting object, what you actually do is request the remote side to create one for you, while you only maintain a proxy. The remote side associates each of such objects with a lease - something that describes how the lifetime of the object is handled. This means that even if the client side still has strong references to the remote object, the remote object can be garbage collected when its lease expires.
The easiest way to check if this happened is using the RemotingServices.GetLifetimeService method. Just use it on the proxy object, and you will get the information you need - for example, CurrentState will tell you if the remote is still alive. If it is, you can also extend the lease using Renew.
So, the usual way to handle the lifetime of the remote object would be - check if it is still alive; if it is, extend the lease and do whatever you want. Make sure you check CurrentLeaseTime too - it's a good idea to maintain it some reasonable value, rather than always Renewing a fixed amount of time.

Getting a System.Error when attempting to Sync using batching in Sync Framework

I am writing an n-tiered application that uses the Sync Framework v2.1 in combination with a WCF web service. My code is based on this example: Database-Sync
Syncing seems to work without a problem. But, when I add the batching functionality I get an error that is proving difficult to debug.
In the client portion of the solution (a WPF app), I have a virtual class that inherits from KnowledgeSyncProvider. It also implements the IDisposable interface. I use this class to call the WCF service I wrote and use the sync functionality there. When the sync code runs (orchestrator.Synchronize() is called), everything seems to work correctly and the EndSession function override that I have written runs without error. But, after execution leaves that function, a System.Error occurs from within Microsoft.Synchronization.CoreInterop.ISyncSession.Start (as far as I can tell).
The functions I have written to provide the IDisposable functionality are never called so, something is happening after EndSession, but before my application's KnowledgeSyncProvider virtual class can be disposed.
Here is the error information:
_message: "System error."
Stack Trace: at Microsoft.Synchronization.CoreInterop.ISyncSession.Start(CONFLICT_RESOLUTION_POLICY resolutionPolicy, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
at Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWaySyncHelper(SyncIdFormatGroup sourceIdFormats, SyncIdFormatGroup destinationIdFormats, KnowledgeSyncProviderConfiguration destinationConfiguration, SyncCallbacks DestinationCallbacks, ISyncProvider sourceProxy, ISyncProvider destinationProxy, ChangeDataAdapter callbackChangeDataAdapter, SyncDataConverter conflictDataConverter, Int32& changesApplied, Int32& changesFailed)
at Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWayKnowledgeSync(SyncDataConverter sourceConverter, SyncDataConverter destinationConverter, SyncProvider sourceProvider, SyncProvider destinationProvider, Int32& changesApplied, Int32& changesFailed)
at Microsoft.Synchronization.KnowledgeSyncOrchestrator.Synchronize()
at Microsoft.Synchronization.SyncOrchestrator.Synchronize()
at MyApplication.Library.SynchronizationHelper.SynchronizeProviders() in C:\My Documents\Visual Studio 2010\Projects\MyApplication\MyApplication\Library\SynchronizationHelper.cs:line 43
at MyApplication.ViewModels.MainViewModel.SynchronizeDatabases() in C:\My Documents\Visual Studio 2010\Projects\MyApplication\MyApplication\ViewModels\MainViewModel.cs:line 46
I have enabled trace info but, that doesn't seem to provide any helpful information in this case.
Can anyone make a suggestion as to how I might be able to figure out what is causing this error?
Thanks in advance for any help you can provide.
Here is the code that handles syncing:
public void SynchronizeProviders()
{
CeDatabase localDb = new CeDatabase();
localDb.Location = Directory.GetCurrentDirectory() + "\Data\SYNCTESTING5.sdf";
RelationalSyncProvider localProvider = ConfigureCeSyncProvider(localDb.Connection);
MyAppKnowledgeSyncProvider srcProvider = new MyAppKnowledgeSyncProvider();
localProvider.MemoryDataCacheSize = 5000;
localProvider.BatchingDirectory = "c:\batchingdir";
srcProvider.BatchingDirectory = "c:\batchingdir";
SyncOrchestrator orchestrator = new SyncOrchestrator();
orchestrator.LocalProvider = localProvider;
orchestrator.RemoteProvider = srcProvider;
orchestrator.Direction = SyncDirectionOrder.UploadAndDownload;
CheckIfProviderNeedsSchema(localProvider as SqlCeSyncProvider, srcProvider);
SyncOperationStatistics stats = orchestrator.Synchronize();
}
And here is the KnowledgeSyncProviderClass that is used to create srcProvider:
class MyAppKnowledgeSyncProvider : KnowledgeSyncProvider, IDisposable
{
protected MyAppSqlSync.IMyAppSqlSync proxy;
protected SyncIdFormatGroup idFormatGroup;
protected DirectoryInfo localBatchingDirectory;
protected bool disposed = false;
private string batchingDirectory = Environment.ExpandEnvironmentVariables("%TEMP%");
public string BatchingDirectory
{
get { return batchingDirectory; }
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("value cannot be null or empty");
}
try
{
Uri uri = new Uri(value);
if (!uri.IsFile || uri.IsUnc)
{
throw new ArgumentException("value must be a local directory");
}
batchingDirectory = value;
}
catch (Exception e)
{
throw new ArgumentException("Invalid batching directory.", e);
}
}
}
public MyAppKnowledgeSyncProvider()
{
this.proxy = new MyAppSqlSync.MyAppSqlSyncClient();
this.proxy.Initialize();
}
public override void BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
{
this.proxy.BeginSession(position);
}
public DbSyncScopeDescription GetScopeDescription()
{
return this.proxy.GetScopeDescription();
}
public override void EndSession(SyncSessionContext syncSessionContext)
{
this.proxy.EndSession();
if (this.localBatchingDirectory != null)
{
this.localBatchingDirectory.Refresh();
if (this.localBatchingDirectory.Exists)
{
this.localBatchingDirectory.Delete(true);
}
}
}
public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
{
MyAppSqlSync.GetChangesParameters changesWrapper = proxy.GetChanges(batchSize, destinationKnowledge);
changeDataRetriever = changesWrapper.DataRetriever;
DbSyncContext context = changeDataRetriever as DbSyncContext;
if (context != null && context.IsDataBatched)
{
if (this.localBatchingDirectory == null)
{
string remotePeerId = context.MadeWithKnowledge.ReplicaId.ToString();
string sessionDir = Path.Combine(this.batchingDirectory, "MyAppSync_" + remotePeerId);
this.localBatchingDirectory = new DirectoryInfo(sessionDir);
if (!this.localBatchingDirectory.Exists)
{
this.localBatchingDirectory.Create();
}
}
string localFileName = Path.Combine(this.localBatchingDirectory.FullName, context.BatchFileName);
FileInfo localFileInfo = new FileInfo(localFileName);
if (!localFileInfo.Exists)
{
byte[] remoteFileContents = this.proxy.DownloadBatchFile(context.BatchFileName);
using (FileStream localFileStream = new FileStream(localFileName, FileMode.Create, FileAccess.Write))
{
localFileStream.Write(remoteFileContents, 0, remoteFileContents.Length);
}
}
context.BatchFileName = localFileName;
}
return changesWrapper.ChangeBatch;
}
public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever)
{
throw new NotImplementedException();
}
public override void GetSyncBatchParameters(out uint batchSize, out SyncKnowledge knowledge)
{
MyAppSqlSync.SyncBatchParameters wrapper = proxy.GetKnowledge();
batchSize = wrapper.BatchSize;
knowledge = wrapper.DestinationKnowledge;
}
public override SyncIdFormatGroup IdFormats
{
get
{
if (idFormatGroup == null)
{
idFormatGroup = new SyncIdFormatGroup();
idFormatGroup.ChangeUnitIdFormat.IsVariableLength = false;
idFormatGroup.ChangeUnitIdFormat.Length = 1;
idFormatGroup.ReplicaIdFormat.IsVariableLength = false;
idFormatGroup.ReplicaIdFormat.Length = 16;
idFormatGroup.ItemIdFormat.IsVariableLength = true;
idFormatGroup.ItemIdFormat.Length = 10 * 1024;
}
return idFormatGroup;
}
}
public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
DbSyncContext context = changeDataRetriever as DbSyncContext;
if (context != null && context.IsDataBatched)
{
string fileName = new FileInfo(context.BatchFileName).Name;
string peerId = context.MadeWithKnowledge.ReplicaId.ToString();
if (!this.proxy.HasUploadedBatchFile(fileName, peerId))
{
FileStream stream = new FileStream(context.BatchFileName, FileMode.Open, FileAccess.Read);
byte[] contents = new byte[stream.Length];
using (stream)
{
stream.Read(contents, 0, contents.Length);
}
this.proxy.UploadBatchFile(fileName, contents, peerId);
}
context.BatchFileName = fileName;
}
SyncSessionStatistics stats = this.proxy.ApplyChanges(resolutionPolicy, sourceChanges, changeDataRetriever);
sessionStatistics.ChangesApplied += stats.ChangesApplied;
sessionStatistics.ChangesFailed += stats.ChangesFailed;
}
public override void ProcessFullEnumerationChangeBatch(ConflictResolutionPolicy resolutionPolicy, FullEnumerationChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
throw new NotImplementedException();
}
~MyAppKnowledgeSyncProvider()
{
Dispose(false);
}
#region IDisposable Members
public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.proxy != null)
{
CloseProxy();
}
}
disposed = true;
}
}
public virtual void CloseProxy()
{
if (this.proxy != null)
{
this.proxy.Cleanup();
ICommunicationObject channel = proxy as ICommunicationObject;
if (channel != null)
{
try
{
channel.Close();
}
catch (TimeoutException)
{
channel.Abort();
}
catch (CommunicationException)
{
channel.Abort();
}
}
this.proxy = null;
}
}
#endregion
}
Bizarrely, it has just started working.
I got a notification within Visual Studio 2010 that I should "update components" or "install components". It installed some web components.
Now, my code runs without error.
While I am very glad to be past this error, I am getting past it in a very unsatisfying way.
Well. I'll take what I can get.

Categories

Resources