關於C# XML序列化的一個BUG的修改
在我前一篇博客中提到用XML序列化作為數據庫的一個方案,@拿筆小心 提到他們在用XML序列化時,遇到了一個比較嚴重的bug,即XML不閉合,系統不能正確的加載此XML。在我的開發經驗中,也遇到過這樣的問題。現在把這個BUG的描述及解決方案記錄如下,也供遇到此BUG的朋友參考。
BUG描述
這個BUG的出現也是比較詭異的,我們給客戶做的一套系統,這個系統會把數據寫到N個xml文件中,正常情況下都沒有問題。直到有一天……客戶運行程序運行了一天,到快下班的時候,把數據保存到數據庫中;第二天來上班時,忽然發現數據都沒有了,也就是說昨天一天的工作白做了。
當客戶把這個BUG告訴我的時候,我第一時間的反應是要重現這個BUG。因為同樣的系統N份已經運行了一年了,從來沒有出現過這個問題。結果客戶在同樣的機器上再次測試,沒有遇到這個問題。我以為這個BUG是偶然現象,就沒有處理,結果噩夢開始了。
當客戶把系統部署到生產系統中之后,生產系統中偶爾也出現這個問題,每次出現這個問題,基本上耽誤了一天的工作,損失都是N萬,當時壓力巨大,趕緊扎到現場解決問題。
我發現之所以以前沒有出現這個BUG,是因為以前的數據量都非常少,但這個版本的數據量很大。我觀察了數據文件,發現是XML文件丟失了一部分結尾造成的。如丟失一個>號,導致不能正確加載XML,數據丟失。
解決方案1
既然定位到了問題,那就有解決方案了。每次寫完數據之后,我都會重新LOAD一下數據以驗證正確性,如果不正確,則重新寫入數據。用這個思路,我很快改了一版,部署到生產環境中。(測試環境很難重新這個錯誤)。
然而,問題還是沒有解決,一個月之后,同樣的問題又出現了。看來必須找到根本原因,不能取巧解決問題。
解決方案2
我開始google這個問題,最后在stackoverflow上找到:xdocument save adding extra characters。他的描述是XML會增加字符,我的問題是會減少字符。
原來XML的寫入方式是:
config.Save(new FileStream(@"c:\foo.xml", FileMode.Create, FileAccess.Write), SaveOptions.None);
應該改為
using (FileStream fs = new FileStream(@"C:\foo.xml", FileMode.Truncate, FileAccess.Read))
{
config.Load(fs);
}
主要修改是把FileMode.Create改為FileMode.Truncate。
Use FileMode.Truncate in your write FileStream so that the file is truncated to 0 bytes before you start writing to it.
這個方案看起來是靠譜的,然而,我還是不放心。
解決方案3
為了防止再次出問題,我寫了一個FixErrorXmlFile方法,解決去除xml多字符的問題,在每次加載xml的時候,如果出現錯誤,調用此方法修正xml錯誤。其核心代碼如下:
private static bool ReadFile(string filePath, out string realContent)
{
string content = string.Empty;
realContent = string.Empty;
using (FileStream fs = new FileStream(filePath, FileMode.Truncate))
{
using (StreamReader sr = new StreamReader(fs))
{
content = sr.ReadToEnd();
}
}
//首先,要找到文件頭末尾的'>'(即第一個右尖括號)的索引值index1,如果index1的值小於1,說明'>'不存在,跳出:否則往下執行
//然后,找到根元素左側的'<'的索引值index2,同樣,如果'<'存在繼續往下執行
// 找到根元素右側的第一個'>'的索引值index3和第一個' '的索引值index4
// 比較index3和index4,較小者為根元素右側的第一個元素的索引
// 找出根元素的名稱
//接着,找到最后一個匹配根元素名稱的開始位置index5
//最后,確定根元素右側第一個'>'的索引值,來獲取文件的真正內容realContent
int index1 = content.IndexOf('>');
if (index1 < 1)
{
return false;
}
int index2 = content.IndexOf("<", index1);
if (index2 < 1)
{
return false;
}
int index3 = content.IndexOf(">", index2);
int index4 = content.IndexOf(" ", index2);
int index = index3 < index4 ? index3 : index4;
string rootName = content.Substring(index2 + 1, index - index2 - 1);
int index5 = content.LastIndexOf(rootName);
if (index5 < 1)
{
return false;
}
index5 += rootName.Length;
realContent = content.Substring(0, index5 + 1);
return true;
}
后記
我同時把解決方案2和解決方案3都修改了,再次放到了生產系統中。一年過去了,再也沒有出現過這個BUG。這個一年指的是同時5台機器一直不停的運行。
后來我又觀察了一下,好像我的解決方案3根本就沒起作用,從來沒有進入過這個函數。也就是說,解決方案2已經解決了這個問題。
雖然這個問題沒有重現,但我還是不認為這個問題已經完美解決。我認為這是微軟的一個BUG,在正常應用序列化的情況下會出現丟失數據,應該由微軟來解決,而不是采用其它的補丁方式解決問題。微軟類似的BUG遇到好幾個了。
總之,這個問題就是這樣解決了,希望對遇到相似問題的人有所幫助。也歡迎大家指出我的問題。
參考:
http://www.cnblogs.com/wardensky/p/4170605.html
我同事當時記錄的這個問題:xml存儲bug