Subject: Queue or how to syncronize without locking
Posted By: mega Post Date: 7/21/2008 2:41:01 AM
Hi all.

I'm re-developing a Data Access Layer Manager (codeplex.com/dalm) and have some architectural doubts about some of the I/O operations.

The problem
All queries are read from an XML file which is loaded into memory on first run. When a query is requested to be executed, the DALM will read from memory. Now, if the query doesn't exist in memory, the XML file has to be read again. But while reading the XML file, queries that does exist in memory should still be retrieved and executed. I have been using ADO.NET DataSet to read XML until now.

1st solution
When the a query is not found, the thread will continue to a "read xml statement" which will be locked using the lock keyword. This way other treads will still be able to access the DataSet. The problem is that the threads that also didn't find their query will continue to a "read xml statement". A solution could be to load all the threads into an Array, fire an event which will load the XML and then call the "find query" method recursively. The array would function as a Queue.

But how does threads behave when you put them in an array? Won't they continue? And if I continue to use DataSet, won't there be a moment where threads will get access denied exceptions, when I change the reference to the new DataSet? Loading grafs XML is not an option as I want the XML file to be human editable, ie. simple.

2nd solution
Dunno - something better? Maybe there is a pattern that doesn't require any locking at all?? I've looked into immutable stacks (http://blogs.msdn.com/ericlippert/archive/2007/12/04/immutability-in-c-part-two-a-simple-immutable-stack.aspx) to see if I could somehow synchronize without locking but I'm not sure how to apply it to my problem.

I hope this triggers some ideas!
Thanks in advance, Jon

 - mega
Moving to C# .NET
Reply By: mega Reply Date: 7/21/2008 5:46:30 AM
Maybe some code would help? Any questions or is it just too tough?

 - mega
Moving to C# .NET
Reply By: planoie Reply Date: 7/21/2008 9:01:02 AM
Are you loading the entire XML file into memory?  If so, I'm not seeing the need for threads or locking.  I suppose it would be sensible to lock the file read and insert into memory step as that only needs to be done once.  (To optimize that you'd probably want two checks on whether the file has been loaded: one prior to and one after the actual lock, as a blocked pass that thinks needs to load the file may no longer need to after it is unblocked by another pass that already did the load.)

What do you mean about how threads behave when in an array?  What's in an array?  A reference to the thread object?  I don't imagine that would have any impact on what is actually happening in the thread itself.  You just have list of references to them.

-Peter
compiledthoughts.com
Reply By: mega Reply Date: 7/21/2008 9:54:29 AM
The class that loads the XML file is static/Shared and loads the entire XML file into a DataSet in the constructor, so its always loaded before any methods is called. But you should still be able to modify the file without having to stop the application. I could check whether the file has been modified before reading from the DataSet but that would make the code slow as threads would need to be locked while checking.

In web scenarios there will always be multiple threads. The DALM retrieves the SQL statements (queries) from the XML file (or in memory) every time a query is executed. In my prototype version (can be downloaded from codeplex.com) I had a check before each request and then locked all threads until the XML file was read. This is what I'm trying to avoid.

About the array of threads, I had an idea that it might just work like that
While reading your post I came up with this, which might do the trick..

private static string LookUp(string queryname, LookUpType type)
    {
        DataTableReader reader = _querySet.CreateDataReader();
        while (reader.Read())
        {
            if (reader["Name"].ToString().Equals(queryname))
            {
                switch (type)
                {
                    case LookUpType.Query :                        
                        return reader["QueryText"].ToString();
                    case LookUpType.Database :                        
                        return reader["Database"].ToString();
                }
            }
        }
        // query not found in memory, update memory from QueryFile
        lock (LookUpLock)
        {
            // could do a check here to see whether the file is loaded or not
            _querySet.ReadXml(string.Format("{0}{1}.xml", QueryFileInfo.FilePath, QueryFileInfo.FileName));
        }
        // call this method again
        return LookUp(queryname, type);
    }

I'll just try it out and get back

 - mega
Moving to C# .NET
Reply By: mega Reply Date: 7/21/2008 10:54:32 AM
Ok, I got it working. Now I just have to watch out for System.StackOverflowException if the query can't be found. That will bring down the entire application. Any ideas?

private static string LookUp(string queryname, LookUpType type)
{
    DataTableReader reader = _querySet.CreateDataReader();
    while (reader.Read())
    {
        if (reader["Name"].ToString().Equals(queryname))
        {
            switch (type)
            {
                case LookUpType.Query :
                    Debug.WriteLine(string.Format("queryname = {0}, answer = {1}", queryname, reader["QueryText"].ToString()), "QueryReader"); 
                    return reader["QueryText"].ToString();
                case LookUpType.Database :
                    Debug.WriteLine(string.Format("queryname = {0}, answer = {1}", queryname, reader["Database"].ToString()), "QueryReader");
                    return reader["Database"].ToString();
            }
        }
    }
    // query not found in memory, update memory from QueryFile
    lock (LookUpLock)
    {
        FileInfo fi = new FileInfo(QueryFileInfo.QualifiedName);
        if (QueryFileInfo.FileLastUpdated < fi.LastWriteTime)
        {
            ReadQueryFile();
            QueryFileInfo.FileLastUpdated = fi.LastWriteTime;
        }
    }
    // I had to have this while testing or I would get a StackOverflowException all the time!
    System.Threading.Thread.Sleep(1000);
    return LookUp(queryname, type);
}
private static void ReadQueryFile()
{
    Debug.WriteLine(string.Format("Reading XML file at {0}", QueryFileInfo.QualifiedName), "QueryReader");
    DataSet ds = new DataSet("Query");            
    ds.ReadXml(QueryFileInfo.QualifiedName);
    _querySet = ds;                       
}


 - mega
Moving to C# .NET
Reply By: mega Reply Date: 7/21/2008 11:10:06 AM
Dunno if I'm getting off topic here but to avoid StackOverflowException, should I create an Array[][] with the thread ids and count,
 so I can terminate the ones that run infinite? Or should I thrust the users to type in query names correctly?

 - mega
Moving to C# .NET
Reply By: planoie Reply Date: 7/21/2008 2:28:56 PM
I'm perplexed by what you are doing.

You are going to loop indefinitely while users type in queries to be saved to an XML file?  What is the purpose of this?  I'm trying to come up with a logical reason for developing this kind of code.

Of course you are going to get a stack overflow the instant a query can't be found because you'll recurse forever (or until the stack crashes).  It would seem logical that the call should simply throw an exception or something else if the query being asked for doesn't exist.  If you recurse forever (even with a delay), how does the user (i.e. developer??) know that the query wasn't found?  This echoes back on the "why are you doing this?" question above.

-Peter
compiledthoughts.com
Reply By: samjudson Reply Date: 7/22/2008 3:02:41 AM
One method would be to refactor your original method so that the dataset lookup is in a seperate method:

private static string LookUp(string queryname, LookUpType type)
{
    string query = LookUpReal(queryname, type);

    if( query != null ) return;

    // query not found in memory, update memory from QueryFile
    lock (LookUpLock)
    {
        FileInfo fi = new FileInfo(QueryFileInfo.QualifiedName);
        if (QueryFileInfo.FileLastUpdated < fi.LastWriteTime)
        {
            ReadQueryFile();
            QueryFileInfo.FileLastUpdated = fi.LastWriteTime;
        }
    }
    return LookUpReal(queryname, type);
}

private static string LookUpReal(string queryname, LookUpType type)
{
    DataTableReader reader = _querySet.CreateDataReader();
    while (reader.Read())
    {
        if (reader["Name"].ToString().Equals(queryname))
        {
            switch (type)
            {
                case LookUpType.Query :
                    Debug.WriteLine(string.Format("queryname = {0}, answer = {1}", queryname, reader["QueryText"].ToString()), "QueryReader"); 
                    return reader["QueryText"].ToString();
                case LookUpType.Database :
                    Debug.WriteLine(string.Format("queryname = {0}, answer = {1}", queryname, reader["Database"].ToString()), "QueryReader");
                    return reader["Database"].ToString();
            }
        }
    }
    return null;
}


/- Sam Judson : Wrox Technical Editor -/
Reply By: mega Reply Date: 7/23/2008 8:46:27 AM
Thanks to both of you. I'm gonna go with your suggestion Sam.

 - thanks, jon

 - mega
Moving to C# .NET

Go to topic 72936

Return to index page 1