mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-11-25 18:52:19 -05:00
Remove multi-threaded kdbx loading path
This commit is contained in:
parent
f157329cab
commit
4f49b073d8
@ -113,7 +113,6 @@
|
||||
<Compile Include="Security\XorredBuffer.cs" />
|
||||
<Compile Include="Security\ProtectedBinary.cs" />
|
||||
<Compile Include="Security\ProtectedString.cs" />
|
||||
<Compile Include="Serialization\AsynchronousBufferedXmlReader.cs" />
|
||||
<Compile Include="Serialization\BinaryReaderEx.cs" />
|
||||
<Compile Include="Serialization\FileLock.cs" />
|
||||
<Compile Include="Serialization\FileTransactionEx.cs" />
|
||||
|
@ -1,367 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace KeePassLib.Serialization
|
||||
{
|
||||
public class AsynchronousBufferedXmlReader : XmlReader
|
||||
{
|
||||
/// <summary>
|
||||
/// An element which indicates the end of the XML document has been reached.
|
||||
/// </summary>
|
||||
private static readonly Element EndMarker = new Element();
|
||||
|
||||
/// <summary>
|
||||
/// The next buffered element available for reading.
|
||||
/// Volatility: only read/written to by non-buffering thread. Passed to the buffer thread as an initial parameter.
|
||||
/// </summary>
|
||||
Element mBufferQueueHead = new Element(); // Start off with the pre-document element. No content, yet.
|
||||
|
||||
private readonly Thread mWorkerThread;
|
||||
private readonly AutoResetEvent mWaitForBuffer = new AutoResetEvent(false);
|
||||
/// <summary>
|
||||
/// True while the reader thread is stalled waiting for buffering.
|
||||
/// Volaitlity: Only written by read thread. Only read by buffer thread
|
||||
/// </summary>
|
||||
private volatile bool mWaitingForBuffer;
|
||||
|
||||
#if TRACE
|
||||
private Stopwatch mReadWaitTimer = new Stopwatch();
|
||||
private Stopwatch mBufferCompletedTimer = new Stopwatch();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Testing helper method
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static XmlReader FullyBuffer(Stream input)
|
||||
{
|
||||
var reader = new AsynchronousBufferedXmlReader();
|
||||
reader.ReadStreamWorker(input);
|
||||
return reader;
|
||||
}
|
||||
|
||||
private AsynchronousBufferedXmlReader()
|
||||
{
|
||||
// Once the end is reached, it stays there.
|
||||
EndMarker.NextElement = EndMarker;
|
||||
}
|
||||
|
||||
public AsynchronousBufferedXmlReader(Stream input) : this()
|
||||
{
|
||||
mWorkerThread = new Thread(ReadStreamWorker) { Name = GetType().Name };
|
||||
mWorkerThread.Start(input);
|
||||
}
|
||||
|
||||
#region Buffering
|
||||
private void ReadStreamWorker(object state)
|
||||
{
|
||||
var input = (Stream)state;
|
||||
|
||||
var xr = XmlReader.Create(input, KdbxFile.CreateStdXmlReaderSettings());
|
||||
|
||||
/// <summary>
|
||||
/// The last buffered element available for reading.
|
||||
/// </summary>
|
||||
Element bufferQueueTail = mBufferQueueHead;
|
||||
|
||||
/// <summary>
|
||||
/// The element currently being buffered. Not yet available for reading.
|
||||
/// </summary>
|
||||
Element currentElement = null;
|
||||
|
||||
while (xr.Read())
|
||||
{
|
||||
switch (xr.NodeType)
|
||||
{
|
||||
case XmlNodeType.Element:
|
||||
// Start a new element
|
||||
if (currentElement != null)
|
||||
{
|
||||
// Add the previous current element to the tail of the buffer
|
||||
bufferQueueTail.NextElement = currentElement;
|
||||
bufferQueueTail = currentElement;
|
||||
if (mWaitingForBuffer) mWaitForBuffer.Set(); // Signal that a new element is available in the buffer
|
||||
}
|
||||
|
||||
currentElement = new Element { Name = xr.Name };
|
||||
|
||||
// Process attributes - current optimisation, all elements have 0 or 1 attribute
|
||||
if (xr.MoveToNextAttribute())
|
||||
{
|
||||
#if DEBUG
|
||||
Debug.Assert(xr.AttributeCount == 1);
|
||||
currentElement.AttributeName = xr.Name;
|
||||
#endif
|
||||
currentElement.AttributeValue = xr.Value;
|
||||
}
|
||||
|
||||
currentElement.IsEmpty = xr.IsEmptyElement;
|
||||
|
||||
break;
|
||||
|
||||
case XmlNodeType.Text:
|
||||
currentElement.Value = xr.Value;
|
||||
currentElement.IsEmpty = true; // Mark as empty because it will have no end element written for it
|
||||
break;
|
||||
|
||||
case XmlNodeType.EndElement:
|
||||
Debug.Assert(currentElement != null, "Ending an element that was never started");
|
||||
|
||||
// If this is an element with children (not one with a value) add an end element marker to the queue
|
||||
if (currentElement.Value == null || currentElement.Name != xr.Name)
|
||||
{
|
||||
bufferQueueTail.NextElement = currentElement;
|
||||
bufferQueueTail = currentElement;
|
||||
if (mWaitingForBuffer) mWaitForBuffer.Set(); // Signal that a new element is available in the buffer
|
||||
|
||||
currentElement = new Element { Name = xr.Name, IsEndElement = true };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Conclude the document, add the final element to the buffer and mark the ending
|
||||
currentElement.NextElement = EndMarker;
|
||||
bufferQueueTail.NextElement = currentElement;
|
||||
bufferQueueTail = currentElement;
|
||||
mWaitForBuffer.Set(); // Signal that final element is available in the buffer (regardless of wait flag, to avoid race condition)
|
||||
#if TRACE
|
||||
mBufferCompletedTimer.Start();
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
private class Element
|
||||
{
|
||||
/// <summary>
|
||||
/// Link to the next buffered element.
|
||||
/// Volatility: Written to by buffer thread only. Read by both threads
|
||||
/// </summary>
|
||||
public volatile Element NextElement;
|
||||
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// If this element marks the end of an xml element with child nodes, the IsEndElement will be true, and Value must be null.
|
||||
/// </summary>
|
||||
public bool IsEndElement;
|
||||
|
||||
/// <summary>
|
||||
/// Set true if this represents an empty element
|
||||
/// </summary>
|
||||
public bool IsEmpty;
|
||||
|
||||
/// <summary>
|
||||
/// If Value is non-null, then there will be no corresponding Element with IsEndElement created.
|
||||
/// </summary>
|
||||
public string Value;
|
||||
|
||||
// Currently KDBX has a maximum of one attribute per element, so no need for a dictionary here, and the name is only used for debug asserts
|
||||
#if DEBUG
|
||||
public string AttributeName;
|
||||
#endif
|
||||
public string AttributeValue;
|
||||
}
|
||||
|
||||
#region Custom XmlReader implementation for usage by KdbxFile only
|
||||
public override bool Read()
|
||||
{
|
||||
Element nextElement;
|
||||
while ((nextElement = mBufferQueueHead.NextElement) == null)
|
||||
{
|
||||
#if TRACE
|
||||
mReadWaitTimer.Start();
|
||||
#endif
|
||||
mWaitingForBuffer = true;
|
||||
mWaitForBuffer.WaitOne();
|
||||
mWaitingForBuffer = false;
|
||||
|
||||
#if TRACE
|
||||
mReadWaitTimer.Stop();
|
||||
#endif
|
||||
}
|
||||
mBufferQueueHead = mBufferQueueHead.NextElement;
|
||||
|
||||
|
||||
#if TRACE
|
||||
if (mBufferQueueHead == EndMarker)
|
||||
{
|
||||
Debug.WriteLine(String.Format("Asynchronous Buffered XmlReader waited for a total of: {0}ms, buffer completed {1}ms ahead of read", mReadWaitTimer.ElapsedMilliseconds, mBufferCompletedTimer.ElapsedMilliseconds));
|
||||
}
|
||||
#endif
|
||||
return mBufferQueueHead != EndMarker;
|
||||
}
|
||||
|
||||
public override string ReadElementString()
|
||||
{
|
||||
var result = mBufferQueueHead.Value ?? String.Empty; // ReadElementString returns empty strings for null content
|
||||
Read(); // Read element string always skips to the start of the next element
|
||||
return result;
|
||||
}
|
||||
|
||||
public override XmlNodeType NodeType
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBufferQueueHead.IsEndElement ? XmlNodeType.EndElement : XmlNodeType.Element;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsEmptyElement
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBufferQueueHead.IsEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBufferQueueHead.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasAttributes
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBufferQueueHead.AttributeValue != null;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool MoveToAttribute(string name)
|
||||
{
|
||||
#if DEBUG
|
||||
Debug.Assert(mBufferQueueHead.AttributeName == name);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return mBufferQueueHead.AttributeValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override bool MoveToElement()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Unimplemented XmlReader overrides
|
||||
|
||||
public override int AttributeCount
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override string BaseURI
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int Depth
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override bool EOF
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override string GetAttribute(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetAttribute(string name, string namespaceURI)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetAttribute(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool HasValue
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override string LocalName
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override string LookupNamespace(string prefix)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool MoveToAttribute(string name, string ns)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool MoveToFirstAttribute()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool MoveToNextAttribute()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override XmlNameTable NameTable
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override string NamespaceURI
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override string Prefix
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override bool ReadAttributeValue()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override ReadState ReadState
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override void ResolveEntity()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -70,8 +70,6 @@ namespace KeePassLib.Serialization
|
||||
Debug.Assert(sSource != null);
|
||||
if(sSource == null) throw new ArgumentNullException("sSource");
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
|
||||
m_format = kdbFormat;
|
||||
m_slLogger = slLogger;
|
||||
|
||||
@ -129,33 +127,7 @@ namespace KeePassLib.Serialization
|
||||
}
|
||||
else m_randomStream = null; // No random stream for plain-text files
|
||||
|
||||
Debug.WriteLine(String.Format("Crypto setup: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
stopWatch.Restart();
|
||||
|
||||
/*
|
||||
var memStream = new MemoryStream((int)hashedStream.Length);
|
||||
CopyStream(readerStream, memStream);
|
||||
readerStream = memStream;
|
||||
Debug.WriteLine(String.Format("CopyStream: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
|
||||
|
||||
stopWatch.Restart();
|
||||
*/
|
||||
|
||||
//var bufferedXmlReader = AsynchronousBufferedXmlReader.FullyBuffer(readerStream);
|
||||
//Debug.WriteLine(String.Format("ReadToBuffer: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
|
||||
if (Java.Lang.Runtime.GetRuntime().AvailableProcessors() > 1)
|
||||
{
|
||||
ReadDocumentStreamed(new AsynchronousBufferedXmlReader(readerStream), hashedStream);
|
||||
Debug.WriteLine(String.Format("ReadDocumentStreamed: {0}ms multi-threaded", stopWatch.ElapsedMilliseconds));
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadXmlStreamed(readerStream, hashedStream);
|
||||
Debug.WriteLine(String.Format("ReadXmlStreamed: {0}ms single-threaded", stopWatch.ElapsedMilliseconds));
|
||||
}
|
||||
stopWatch.Restart();
|
||||
ReadXmlStreamed(readerStream, hashedStream);
|
||||
// ReadXmlDom(readerStream);
|
||||
|
||||
readerStream.Close();
|
||||
@ -166,26 +138,9 @@ namespace KeePassLib.Serialization
|
||||
{
|
||||
throw new CryptographicException(KLRes.FileCorrupted);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
CommonCleanUpRead(sSource, hashedStream);
|
||||
Debug.WriteLine(String.Format("Close and Clean Up: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
}
|
||||
finally { CommonCleanUpRead(sSource, hashedStream); }
|
||||
}
|
||||
|
||||
/*
|
||||
public static void CopyStream(Stream input, Stream output)
|
||||
{
|
||||
byte[] buffer = new byte[32768];
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
output.Write(buffer, 0, read);
|
||||
}
|
||||
output.Seek(0, SeekOrigin.Begin);
|
||||
}*/
|
||||
|
||||
private void CommonCleanUpRead(Stream sSource, HashingStreamEx hashedStream)
|
||||
{
|
||||
hashedStream.Close();
|
||||
|
@ -120,8 +120,6 @@ namespace keepass2android
|
||||
|
||||
public void LoadData(Context ctx, IOConnectionInfo iocInfo, String password, String keyfile, UpdateStatus status)
|
||||
{
|
||||
var stopWatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
mIoc = iocInfo;
|
||||
|
||||
KeePassLib.PwDatabase pwDatabase = new KeePassLib.PwDatabase();
|
||||
@ -140,14 +138,8 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("LoadData Pre-open: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
stopWatch.Restart();
|
||||
|
||||
pwDatabase.Open(iocInfo, key, status);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("LoadData Open: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
stopWatch.Restart();
|
||||
|
||||
if (iocInfo.IsLocalFile())
|
||||
{
|
||||
mLastChangeDate = System.IO.File.GetLastWriteTimeUtc(iocInfo.Path);
|
||||
@ -163,8 +155,6 @@ namespace keepass2android
|
||||
Loaded = true;
|
||||
pm = pwDatabase;
|
||||
searchHelper = new SearchDbHelper(ctx);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("LoadData Post-open: {0}ms", stopWatch.ElapsedMilliseconds));
|
||||
}
|
||||
|
||||
bool quickUnlockEnabled = false;
|
||||
@ -197,7 +187,12 @@ namespace keepass2android
|
||||
|
||||
public PwGroup Search(SearchParameters searchParams)
|
||||
{
|
||||
return searchHelper.search(this, searchParams);
|
||||
return Search(searchParams, null);
|
||||
}
|
||||
|
||||
public PwGroup Search(SearchParameters searchParams, IDictionary<PwUuid, String> resultContexts)
|
||||
{
|
||||
return searchHelper.search(this, searchParams, resultContexts);
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user