eWasOpenForRead Error When BlockTableRecord.ModelSpace Is Opened For Write

eWasOpenForRead Error When BlockTableRecord.ModelSpace Is Opened For Write

Anonymous
Not applicable
1,131 Views
6 Replies
Message 1 of 7

eWasOpenForRead Error When BlockTableRecord.ModelSpace Is Opened For Write

Anonymous
Not applicable

After implementing a system for caching the state of all dynamic blocks managed by an existing plugin in order to avoid unessesary reading/writing to dynamic block properties, an issue which never occured before appeared. The changes described have the effect of dramatically reducing reads/writes to dynamic block properties. Some intensive operations in the plugin run much faster because of these optimizations, and much less memory is utilized by acad.exe (setting dynamic block properties to the same value as is current causes memory consumption). This is a related post which has caused me to make the described optimizations (along with a desire for faster performance): http://forums.autodesk.com/t5/net/net-plugin-causes-high-memory-consumption-leak-not-in-managed/td-p...

 

The issue appears randomly, though the more times certain operations are performed, the more likely the issue is to occur. After the issue occurs, any attempt to open the BlockTableRecord for ModelSpace for write results in an exception with the message eWasOpenForRead, making it impossible to insert new block instances in the drawing, at least using my code. 

 

I'm wondering if any of you have advice about how to force the read lock on this record to close programatically. Or if you have advice on how to enumerate the locks on this BlockTableRecord to help me debug? Or a description of what might cause this situation to occur? It seems a transaction may not be properly Committed/Aborted or otherwise disposed of. Though all of my code involving drawing database transactions is wrapped in using blocks and either commit or abort is always called. There should be no open drawing database transactions when this function is called since I only have this one plugin loaded and all transactions are performed on the main thread.

 

Here is the code for the function where the error occurs (it is responsible for inserting a block instance into the drawing and a few related activities like setting the layer the block instance resides on):

 

 

    Public Shared Function InsertBlock(ByVal DatabaseIn As Database, ByVal BTRToAddTo As String, ByVal InsPt As Geometry.Point3d, ByVal BlockName As String, ByVal XScale As Double, ByVal YScale As Double, ByVal ZScale As Double, ByVal LayerName As String, ByVal TransformBy As Matrix3d, Optional ByVal Rotation As Double = 0) As DatabaseServices.ObjectId
        If String.IsNullOrEmpty(LayerName) Then Throw New ArgumentNullException("LayerName")
        If Not LayerHelper.LayerExists(DatabaseIn, LayerName) Then
            Dim layerManager As ACADLayerCache = ACADLayerCacheSingleton.Instance()
            Dim layerColor As System.Drawing.Color? = layerManager.GetLayerColor(LayerName)
            If layerColor IsNot Nothing Then
                LayerHelper.AddLayer(DatabaseIn, LayerName, Autodesk.AutoCAD.Colors.Color.FromColor(layerColor.Value))
            Else
                LayerHelper.AddLayer(DatabaseIn, LayerName, Autodesk.AutoCAD.Colors.Color.FromColor(Drawing.Color.White))
            End If
        End If
        Dim id As ObjectId = Nothing
        Dim doc As Document = ApplicationServices.Application.DocumentManager.MdiActiveDocument
        Using lock As DocumentLock = doc.LockDocument()
            Using trans As Transaction = DatabaseIn.TransactionManager.StartOpenCloseTransaction()
                Try
                    Using myBlockTable As BlockTable = trans.GetObject(DatabaseIn.BlockTableId, OpenMode.ForRead)
                        If myBlockTable.Has(BlockName) = False Then
                            Return Nothing
                        End If
                        'If the specified BlockTableRecord does not exist, return
                        If myBlockTable.Has(BTRToAddTo) = False Then
                            Return Nothing
                        End If
                        Using myBlockDef As BlockTableRecord = trans.GetObject(myBlockTable(BlockName), OpenMode.ForRead)
                            Using myBlockTableRecord As BlockTableRecord = trans.GetObject(myBlockTable(BTRToAddTo), OpenMode.ForWrite)
                                'Create a new BlockReference 
                                Using myBlockRef As New BlockReference(InsPt, myBlockDef.Id)
                                    'Set the scale factors 
                                    myBlockRef.ScaleFactors = New Geometry.Scale3d(XScale, YScale, ZScale)
                                    'Add the new BlockReference to the specified BlockTableRecord 
                                    myBlockRef.Rotation = Rotation
                                    myBlockRef.TransformBy(TransformBy)
                                    myBlockTableRecord.AppendEntity(myBlockRef)
                                    'Add the BlockReference to the BlockTableRecord. 
                                    trans.AddNewlyCreatedDBObject(myBlockRef, True)
                                    Using blockEnt As Entity = trans.GetObject(myBlockRef.Id, OpenMode.ForWrite)
                                        Try
                                            blockEnt.Layer = LayerName
                                        Catch ex As System.Exception

                                        End Try
                                        Dim myAttColl As DatabaseServices.AttributeCollection = myBlockRef.AttributeCollection
                                        'Find Attributes and add them to the AttributeCollection 
                                        'of the BlockReference 
                                        For Each myEntID As ObjectId In myBlockDef
                                            Using myEnt As Entity = trans.GetObject(myEntID, OpenMode.ForWrite)
                                                'myEnt.Layer = LayerName
                                                If TypeOf myEnt Is DatabaseServices.AttributeDefinition Then
                                                    Dim myAttDef As DatabaseServices.AttributeDefinition = myEnt
                                                    Using myAttRef As New DatabaseServices.AttributeReference
                                                        myAttRef.SetAttributeFromBlock(myAttDef, myBlockRef.BlockTransform)
                                                        myAttColl.AppendAttribute(myAttRef)
                                                        trans.AddNewlyCreatedDBObject(myAttRef, True)
                                                    End Using
                                                End If
                                            End Using
                                        Next
                                        trans.Commit()
                                        id = myBlockRef.Id
                                    End Using
                                End Using
                            End Using
                        End Using
                    End Using
                Catch ex As System.Exception
                    trans.Abort()
                    Try
                        EventLog.WriteEntry("NiC", "DynamicBlockHelper.InsertBlock - " + ex.GetType().Name + ": " + ex.Message + Environment.NewLine + ex.StackTrace, EventLogEntryType.Error)
                    Catch eex As System.Exception

                    End Try
                End Try
            End Using
        End Using
        Return id
    End Function

 

 

I highlighted the line of code where the exception occurs in red. Note that I handle the exception by checking the validity of the ObjectID returned by the function.

 

If no one has suggestions on how to track down the source of this problem or how to force the BlockTableRecord to close, I will put together a working example plugin which can be used to reproduce the error. Posting more of the plugin code here just isn't practical because of how much of it there is. I realize I'm not giving you much to go on at this point. I just hoping someone can provide some advice.

 

Or maybe I will find a solution on my own, in which case I'll post it here. 

 

Thanks,

Kevin

0 Likes
1,132 Views
6 Replies
Replies (6)
Message 2 of 7

Balaji_Ram
Alumni
Alumni

Hi Kevin,

 

I do not see any obvious issues with that code snippet.

 

You can try switching over to normal transaction using the "StartTransaction" instead of "StartOpenCloseTransaction" to see if the problem is still reproducible. I do agree that the "StartOpenCloseTransaction" is much more efficient when dealing with smaller number of entities, but changing it can provide some clues to proceed as the entity seems to be open elsewhere by another transaction.

 

Also, if you are dealing with large number of entities, the "StartTransaction" would be a better choice in terms of performance. It is only the initialization overhead that is more as compared to "StartOpenCloseTransaction". So, if you can hold on to the transaction while processing large number of entities, that should still perform.

 

I do not think there is a way to forcibly release the entity of its read lock. 

 

If you can provide a buildable sample project to demonstrate the issue, we can try and resolve that.

Please do not share any information that you consider confidential.

 

Regards,

Balaji

 



Balaji
Developer Technical Services
Autodesk Developer Network

0 Likes
Message 3 of 7

Anonymous
Not applicable

Sorry, I have been on vacation. There are issues with the code I provided, though none that have prevented it from working reliably for some time. Most of it came from a forum post on inserting dynamic blocks, I think. I will rewrite it. But it does not seem to be the source of the problem.

 

I was able to (seemingly) eliminate this error by avoiding many unnecessary calls to the following method (these calls were occurring perhaps hundreds of times in a very short span of time before my optimization).

 

    Public Shared Sub SetBlockExtensionDictionaryTextEntries(BlockID As ObjectId, Entries As Dictionary(Of String, String))
        If Entries Is Nothing Then Throw New ArgumentNullException("Entries")
        Dim stateCache As BlockStateCache = BlockStateCacheManager.GetBlockStateCache(BlockID.Database)
        Dim entriesSet As Boolean = False
        If BlockStateCache.EnableBlockStateCacheOptimizations Then
            Dim changeDetected As Boolean = False
            For Each entry In Entries
                If stateCache.BlockAttributeChanged(BlockID.Handle.Value, entry.Key, entry.Value) Then
                    changeDetected = True
                    Exit For
                End If
            Next
            If Not changeDetected Then Return
        End If
        Dim doc As Document = ApplicationServices.Application.DocumentManager.GetDocument(BlockID.Database)
        Using lock As DocumentLock = doc.LockDocument()
            Using trans As Transaction = BlockID.Database.TransactionManager.StartOpenCloseTransaction()
                Using blockRef As BlockReference = trans.GetObject(BlockID, OpenMode.ForWrite)
                    Try
                        If blockRef.ExtensionDictionary.IsNull Then
                            blockRef.CreateExtensionDictionary()
                        End If
                        Using extensionDictionary As DBDictionary = trans.GetObject(blockRef.ExtensionDictionary, OpenMode.ForWrite)
                            For Each dictEntry As KeyValuePair(Of String, String) In Entries
                                Dim entryKey As String = dictEntry.Key
                                Dim entryValue As String = dictEntry.Value
                                If entryValue Is Nothing Then Throw New ApplicationException("Dictionary entry '" + entryKey + "' contains null value.")
                                If entryValue.Length > 1024 Then Throw New ApplicationException("Dictionary entry '" + entryKey + "' contains value which exceeds maximum character length of 1024.")

                                If extensionDictionary.Contains(entryKey) Then
                                    'XRecord exists in dictionary. Update value.
                                    Using xRec As Xrecord = trans.GetObject(extensionDictionary.GetAt(entryKey), OpenMode.ForWrite)
                                        Using buffer As New ResultBuffer()
                                            buffer.Add(New TypedValue(DxfCode.Text, entryValue))
                                            xRec.Data = buffer
                                        End Using
                                    End Using
                                Else
                                    'Dictionary does not contain matching entry. Create new XRecord and add to dictionary.
                                    Using buffer As New ResultBuffer()
                                        buffer.Add(New TypedValue(DxfCode.Text, entryValue))
                                        Using xRec As New Xrecord()
                                            xRec.Data = buffer
                                            extensionDictionary.SetAt(entryKey, xRec)
                                            trans.AddNewlyCreatedDBObject(xRec, True)
                                        End Using
                                    End Using
                                End If
                            Next
                        End Using
                        trans.Commit()
                        entriesSet = True
                    Catch ex As System.Exception
                        Try
                            EventLog.WriteEntry("NiC", "DynamicBlockHelper.SetBlockExtensionDictionaryTextEntries - " + ex.GetType().Name + ": " + ex.Message + Environment.NewLine + ex.StackTrace, EventLogEntryType.Error)
                        Catch eex As System.Exception

                        End Try
                        trans.Abort()
                        Throw ex
                    End Try
                End Using
            End Using
        End Using
        If entriesSet AndAlso BlockStateCache.EnableBlockStateCacheOptimizations Then
            For Each entry In Entries
                If Not stateCache.SetBlockAttribute(BlockID.Handle.Value, entry.Key, entry.Value) Then
                    stateCache.ClearAttributeCache(BlockID.Handle.Value)
                    Return
                End If
            Next
        Else
            stateCache.ClearAttributeCache(BlockID.Handle.Value)
        End If
    End Sub

I've highlighted the important code. Any idea why 10s or 100s of calls to this method in a few seconds would cause the eWasOpenForRead error I have described? This code has been working reliably for years. 

 

Maybe the problem will reappear on client machines once I release an update for my plugin, but for now I can no longer reproduce the exception.

0 Likes
Message 4 of 7

Balaji_Ram
Alumni
Alumni

Hi Kevin,

 

I will need to reproduce the behavior at my end.

It is hard to say what could be causing it with only the code snippet.

 

Can you please share a buildable sample project ?

That should help me reproduce the problem and know how the code gets called repeatedly.

 

Regards,

Balaji

 

 

 



Balaji
Developer Technical Services
Autodesk Developer Network

0 Likes
Message 5 of 7

Anonymous
Not applicable

This issue has continued to be a problem for me over the last few weeks, although as I've optimized to avoid unnecessary transactions against the drawing database, the issue has happened less often and is harder to reproduce. 

 

Providing you with the source for the entire plugin is not practical. There are various sensitive credentials in several projects which only run on servers, but are part of the same solution. And there are dependencies which I cannot provide you easily. So I would have to reorganize my solution and package certain dependencies so that you could successfully compile. 

 

I dont suppose if I provide you with the installer for the plugin and the debug files for my assemblies and instructions for reproducing the problem, you could figure out how this is occurring? Or at least give me a hint from what you see in your debugger or some other tools you have which I do not?

 

The problem is not as simple as any one piece of code consistently producing the error. The only way to reproduce the problem is to run the code many times until it occurs. It will occur at different times on different runs on the same machine with the same actions taken. 

 

I may be forced to find a way to reproduce it with a plugin that only includes the code needed to create the problem, which will likely prove time quite consuming... The issue has occurred a few times for a few users during beta testing of a new release of my plugin. I have added a mitigation which detects the error, and forces the drawing to be closed and reopened. If the issue affects many users after general release, I will spend the time to make a plugin. 

 

It seems to me that all that changed in my plugin is speed. I made many optimizations, and as a result the same number of transactions from the same code are being executed in a much shorter time span. Adding optimizations to simply avoid any unnecessary transactions makes the problem much more difficult to reproduce, but it still occurs occasionally.

 

Thanks,

Kevin

0 Likes
Message 6 of 7

Anonymous
Not applicable

Note that this issue has now been reproduced on Windows 7 and 8.1, AutoCAD 2014 and 2015 (fully patched).

0 Likes
Message 7 of 7

Balaji_Ram
Alumni
Alumni

Sorry, It will not be possible for me to investigate it or ask our engineering team for further help with only the binaries.

 

I will need a reproducible buildable sample project to better understand the use of transactions in your code.

 

As you mentioned about several optimizations in using transactions, you may just need to comment out some of those

to narrow down what is causing the issue.

 

Regards,

Balaji

 

 



Balaji
Developer Technical Services
Autodesk Developer Network

0 Likes