Our production server managed to reach a state in which OpenXML document modification wouldn't work anymore. The stack trace from the event log looks like this:
Timestamp: 25/03/2015 05:48:27
1 : Exception has been thrown by the target of an invocation: at blahblahblah.CreateDocumentsFromTemplate(DocumentTemplateData[] data, Int32 templateID, blah blah) in c:\Build\blah.cs:line 1212
2 : Exception has been thrown by the target of an invocation: at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache)
at System.Activator.CreateInstance[T]()
at DocumentFormat.OpenXml.Packaging.OpenXmlPart.LoadDomTree[T()]
at DocumentFormat.OpenXml.Packaging.MainDocumentPart.get_Document()
at blahblah.OpenXMLRenderer.GetWordReport(String filename, DataSet dataset, Dictionary`2 values, List`1 img) in c:\Build\blah.cs:line 297
at blah.DocumentFromTemplate.Render(Int32 letterID, String[] list, DataSet documentTables, Int32 officeID, List`1 img, Int32 orgUnit, Int32 fileID) in c:\Build\blah.cs:line 172
at blah.CreateDocumentsFromTemplate(DocumentTemplateData[] data, Int32 templateID, blah blah) in c:\Build\blah.cs:line 1072
3 : Value cannot be null
Parameter name: key : at System.Xml.NameTable.Add(String key)
at DocumentFormat.OpenXml.OpenXmlElementContext.Init()
at DocumentFormat.OpenXml.OpenXmlPartRootElement..ctor()
As far as I can see, the explanation is like this:
There's a constructor being called by reflection – OpenXmlPartRootElement() – which is in turn calling System.XmlNameTable.Add(key) with a null value for key.
Using Reflector, we can see that the constructor being called is actually the base constructor forDocumentFormat.OpenXml.Wordprocessing.Document– its base type is OpenXmlPartRootElement. Its constructor is calling in turn the constructor for OpenXmlElementContext in order toinitialize its member variable. Inside this constructor – in a call to the method OpenXmlElementContext.Init()– was the ArgumentNullException from calling NameTable.Add.
Here is the reflected code (using ILSpy) for Init():
// DocumentFormat.OpenXml.OpenXmlElementContext
private void Init(){
for (int i = 1; i < NamespaceIdMap.Count; i++) { this._xmlNameTable.Add(NamespaceIdMap.GetNamespaceUri((byte)i));
}
this._xmlNameTable.Add("http://www.w3.org/2000/xmlns/");
this.XmlReaderSettings = new XmlReaderSettings();
this.XmlReaderSettings.NameTable = this.XmlNameTable;
this.XmlReaderSettings.IgnoreWhitespace = true;
}
As seen here, there are two calls to NameTable.Add: one inside a for loop, and one with a string constant. We can rule out the string constant – it will never be null.
Inside the loop, we run through the numbers 1 to the Count property of NamespaceIdMap. For each of these, we retrieve the value of GetNamespaceUri(i).
Inlining the code called by GetNamespaceUri, we have the following:
// DocumentFormat.OpenXml.NamespaceIdMap
private static string[] _namespaceList = new string[]{ "", "http://www.w3.org/XML/1998/namespace", "http://schemas.openxmlformats.org/package/2006/metadata/core-properties", "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties", "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes", "http://purl.org/dc/elements/1.1/",
// … many more like this
}
private static XmlNamespaceManager _xmlNamespaceManager;
private static object[] _namespaceObjectList;
public static string GetNamespaceUri(byte namespaceId){
//inlined: call to static method NamespaceIdMap.MakeSureInitialized();
if (NamespaceIdMap._xmlNamespaceManager == null) {
XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(NamespaceIdMap.GlobalNamespaceNameTable);
object[] array = new object[NamespaceIdMap._namespaceList.Length];
for (int i = 0; i < NamespaceIdMap._namespaceList.Length; i++) { xmlNamespaceManager.AddNamespace(NamespaceIdMap._namespacePrefixList[i], NamespaceIdMap._namespaceList[i]);
object obj = NamespaceIdMap.GlobalNamespaceNameTable.Get(NamespaceIdMap._namespaceList[i]);
array[i] = obj;
}
NamespaceIdMap._xmlNamespaceManager = xmlNamespaceManager;
NamespaceIdMap._namespaceObjectList = array;
}
//end inlined
return (string)NamespaceIdMap._namespaceObjectList[(int)namespaceId];
}
Here we see used the non-const, non-readonly static fields _xmlNamespaceManager, _namespaceObjectList, and the static property GlobalNamespaceNameTable that essentially wraps yet another static member (though of a different class).
No locking is used in any part of the library that we examined in ILSpy.
It is conceivable that at some point, a race condition in the OpenXml library caused any one of these values to be corrupted, leading to one or more null entries in the _namespaceObjectList array.
Evidence of this: when the server was restarted, the issue did not recur.
Is there a known fix for this, or some configuration we could do to prevent this from happening?