Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Core / MS / Internal / IO / Packaging / NetStream.cs / 2 / NetStream.cs
//------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // An object to provide a stream that supports a RCW-ed COM ILockBytes interface on top of a // managed class providing for progressivity through multiple simultaneous WebRequests. // // Notes: // Most of this code need not be re-entrant because only one thread is ever operating here. // The ReadCallback is the only code that will be entered on a separate thread. // // The temp file is shared with the ByteRangeDownloader object (if in use) which is why // all access to the stream is protected by Mutex. // // History: // 10/20/2003: [....]: Reworked from ILockBytes to STream interface to simplify inter-assembly // operation. // 11/07/2003: [....]: Don't call BeginRead from ReadCallBack - this now is recursive and // causes stack overflow // 10/11/2005: [....]: Security Mitigation and Performance changes. // - only allocate byteRangeReadEvent if it might be used // - narrow IsolatedStorage scope to User level (GetUserStoreForDomain) // - re-enabled tracing // - removed Closed property // - introduce checked{} keyword where integers could overflow // - improved Length perf for non-cooperative servers // - return earlier when data is available from byte-range requests // - corrected block-merge logic to merge adjacent blocks // - documented what is and is not protected by _syncLock // 11/10/2005: [....]: Dispose logic // - don't Release the mutex unless we won it (if we didn't time-out waiting) // - encapsulate finally block within lock() statement to ensure that the // finally is executed while we hold the lock // - always call base.Dispose() regardless of our state // [....]: SyncObject was defined as static // - _syncObject should be per-instance because it only shields access // to instance variables. //----------------------------------------------------------------------------- #if DEBUG #define TRACE #endif using System; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Threading; using System.Collections; // for IComparer using System.Diagnostics; // for Debug.Assert using System.Security; // SecurityCritical, SecurityTreatAsSafe using System.Security.Permissions; // for FileIOPermission using System.IO.IsolatedStorage; // for IsolatedStorageFileStream using MS.Internal.IO.Packaging; // ByteRangeDownloader using MS.Internal.PresentationCore; // for ExceptionStringTable namespace MS.Internal.IO.Packaging { ////// Implements a Stream to support ILockBytes. This supports progressive download for performant file access over HTTP /// ///NetStream spawns two download requests. One downloads the entire file and the other /// downloads portions as needed by calls to Read(). internal class NetStream: Stream { //----------------------------------------------------- // // Constructors // //----------------------------------------------------- ////// Constructor /// /// stream we are based on /// URI to access - not marked as critical so no guarantees that it will remain private /// actual length of responseStream (which does not support Length call) /// the original request that was used to get the responseStream /// the original response that was used to get the responseStream ////// Critical /// 1) modifies Critical collection _readEventHandles /// 2) accepts originalRequest which is Critical (not Safe) /// Safe /// 1) _readEventHandles is Critical for set but this class is creating the new ones here /// [SecurityCritical] internal NetStream( Stream responseStream, long fullStreamLength, Uri uri, WebRequest originalRequest, WebResponse originalResponse) { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.NetStream()"); #endif // check parms Invariant.Assert(uri != null); Invariant.Assert(responseStream != null); Invariant.Assert(originalRequest != null); Invariant.Assert(originalResponse != null); // use this to resolve random requests _uri = uri; // uri we are reading from _fullStreamLength = fullStreamLength; _responseStream = responseStream; _originalRequest = originalRequest; // only attempt out-of-order requests on well-behaved HTTP servers // (Note: MSDN indicates that uri.Scheme is always lower case) if (fullStreamLength > 0 && ((String.Compare(uri.Scheme, Uri.UriSchemeHttp, StringComparison.Ordinal) == 0) || (String.Compare(uri.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) == 0))) { _allowByteRangeRequests = true; _readEventHandles[(int)ReadEvent.ByteRangeReadEvent] = new AutoResetEvent(false); } // read events - two sources of data. These events are signalled to indicate that new data is // available in the temp file _readEventHandles[(int)ReadEvent.FullDownloadReadEvent] = new AutoResetEvent(false); // we need to start this StartFullDownload(); } //------------------------------------------------------ // // Public Methods // //----------------------------------------------------- #region Stream Interface ////// Return the bytes requested /// /// destination buffer /// offset to write into that buffer /// how many bytes requested ///how many bytes were written into buffer ///blocks until data is available public override int Read(byte[] buffer, int offset, int count) { CheckDisposed(); #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.Read() offset:{0} length:{1}", _position, count ); #endif PackagingUtilities.VerifyStreamReadArgs(this, buffer, offset, count); // quick exit if (count == 0) return count; int bytesRead = 0; checked { if (offset + count > buffer.Length) throw new ArgumentException(SR.Get(SRID.IOBufferOverflow), "buffer"); // make sure some data is in the stream - block until it is int bytesAvailable = GetData(new Block(_position, count)); count = Math.Min(bytesAvailable, count); // don't return more than they requested, and don't return more than is available // read into the buffer and return (if any data is available) if (count > 0) { try { _tempFileMutex.WaitOne(); _tempFileStream.Seek(_position, SeekOrigin.Begin); // align the temp stream with our logical position bytesRead = _tempFileStream.Read(buffer, offset, count); // read from the temp file } finally { _tempFileMutex.ReleaseMutex(); } // Update our position - we do this last because the Stream contract guarantees the position is only updated // if the Read() call was successful. _position += bytesRead; } } return bytesRead; } ////// Is stream readable? /// public override bool CanRead { get { return !_disposed; // always true if we are not disposed } } ////// Is stream seekable? /// ///We MUST support seek as this is used to implement ILockBytes.ReadAt() public override bool CanSeek { get { return !_disposed; // always true if we are not disposed } } ////// Is stream writeable? /// public override bool CanWrite { get { return false; // we never support writing } } ////// Seek /// /// offset from origin /// origin of seek ///zero ///SeekOrigin.End can be expensive when operating against a server that fails to report the full length of the /// resource being downloaded. Use SeekOrigin.Begin or SeekOrigin.Current if possible. public override long Seek(long offset, SeekOrigin origin) { CheckDisposed(); long temp = 0; checked { switch (origin) { case SeekOrigin.Begin: { temp = offset; break; } case SeekOrigin.Current: { temp = _position + offset; break; } case SeekOrigin.End: { temp = Length + offset; break; } default: { throw new ArgumentOutOfRangeException("origin", SR.Get(SRID.SeekOriginInvalid)); } } } if (temp < 0) { throw new ArgumentException(SR.Get(SRID.SeekNegative)); } #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.Seek() pos:{0}", temp); #endif _position = temp; return _position; } ////// Logical byte position in this stream /// public override long Position { get { CheckDisposed(); return _position; } set { CheckDisposed(); if (value < 0) throw new ArgumentException(SR.Get(SRID.SeekNegative)); #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.set_Position() pos:{0}", value); #endif _position = value; } } ////// SetLength /// ///not supported public override void SetLength(long newLength) { throw new NotSupportedException(SR.Get(SRID.SetLengthNotSupported)); } ////// Write /// ///not supported public override void Write(byte[] buf, int offset, int count) { throw new NotSupportedException(SR.Get(SRID.WriteNotSupported)); } ////// Length /// public override long Length { get { CheckDisposed(); // handle ftp servers that don't return a length if (_fullStreamLength < 0) { // fallback for servers that refuse to provide the length of the resource checked { // Length could not be determined so we need to block our caller // while reading the entire stream to determine the length long temp = _position; // squirrel away for later _position = _highWaterMark; // make sure we get the full length in case they seek'd before call get_Length byte[] buf = new byte[0x1000]; // when this while loop exits, _fullStreamLength contains the length of the stream while (Read(buf, 0, buf.Length) > 0) ; // restore _position = temp; } } return _fullStreamLength; } } ////// Flush /// public override void Flush() { // ignore flush calls as we are read-only by definition } #endregion // Stream //------------------------------------------------------ // // Protected Methods // //------------------------------------------------------ ////// Dispose /// /// ///PreSharp 6519 dictates that we not throw exceptions from Dispose() methods. ////// Critical /// 1) modifies Critical collection _readEventHandles /// Safe /// 1) _readEventHandles is Critical for set but we are disposing the ones this class created /// [SecurityCritical, SecurityTreatAsSafe] protected override void Dispose(bool disposing) { // always call base.Dispose(bool) regardless of our state try { if (disposing) { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.Close()"); #endif lock (_syncObject) { // ignore multiple calls if (_disposed) return; try { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.Dispose(bool) - mark as closed"); #endif // No matter what, mark ourselves as disposed. // This is critical to prevent race condition. _disposed = true; // release any blocked threads - Set() does not throw any exceptions if (_readEventHandles[(int)ReadEvent.FullDownloadReadEvent] != null) _readEventHandles[(int)ReadEvent.FullDownloadReadEvent].Set(); if (_readEventHandles[(int)ReadEvent.ByteRangeReadEvent] != null) _readEventHandles[(int)ReadEvent.ByteRangeReadEvent].Set(); // Free ByteRangeDownloader FreeByteRangeDownloader(); // Free Event Handles - should not throw if (_readEventHandles[(int)ReadEvent.FullDownloadReadEvent] != null) { _readEventHandles[(int)ReadEvent.FullDownloadReadEvent].Close(); _readEventHandles[(int)ReadEvent.FullDownloadReadEvent] = null; } if (_readEventHandles[(int)ReadEvent.ByteRangeReadEvent] != null) { _readEventHandles[(int)ReadEvent.ByteRangeReadEvent].Close(); _readEventHandles[(int)ReadEvent.ByteRangeReadEvent] = null; } // Free Full Download if (_responseStream != null) { _responseStream.Close(); } FreeTempFile(); #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.Dispose(bool) - exiting"); #endif } finally { // final housekeeping _responseStream = null; _readEventHandles = null; _byteRangesAvailable = null; _readBuf = null; } } } } finally { base.Dispose(disposing); } } //----------------------------------------------------- // // Internal Methods // //------------------------------------------------------ //----------------------------------------------------- // // Private Methods // //----------------------------------------------------- ////// Starts the asynchronous full-file download request - after the response is available /// ///byte-range requests will be entertained as appropriate during Read() calls private void StartFullDownload() { _highWaterMark = 0; _readBuf = new byte[_bufferSize]; // open the stream for read and write with a retry count of 3 (we try 3 times before giving up on name // collision) // no need for mutex because this is guaranteed to be the first access (ByteRangeDownloader not yet created // and BeginRead not yet started) _tempFileStream = PackagingUtilities.CreateUserScopedIsolatedStorageFileStreamWithRandomName( 3, out _tempFileName); // initiate the data retrieval - must do this at least once to kick off the process _responseStream.BeginRead(_readBuf, 0, _readBuf.Length, new AsyncCallback(ReadCallBack), this); } ////// Throw exception if we are already closed /// private void CheckDisposed() { if (_disposed) throw new ObjectDisposedException("Stream"); } #region FullDownload ////// ReadCallBack /// /// async read result containing our NetLockBytes reference ///This method is called back when an async read is complete ////// Critical /// 1) accesses Critical collection _readEventHandles /// Safe /// 1) _readEventHandles is Critical for set /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCallBack(IAsyncResult ar) { // prevent simultaneous BeginRead/EndRead // after this lock is released, either _highWaterMark or _fullDownloadComplete is updated (or we are closed) lock (_syncObject) { // make sure we always signal the event, even when we exit early (see finally clause) try { // exit early if we are closed if (_disposed) { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.ReadCallBack() - exiting early because we are closed"); #endif return; } // verify that it contains data int read = _responseStream.EndRead(ar); if (read > 0) { // append the data to our temp file // synchronize access to the file try { _tempFileMutex.WaitOne(); #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.ReadCallBack (offset,length):({0},{1})", _highWaterMark, read); #endif _tempFileStream.Seek(_highWaterMark, SeekOrigin.Begin); _tempFileStream.Write(_readBuf, 0, read); _tempFileStream.Flush(); // force flush because we are sharing this file with ByteRangeDownloader checked { _highWaterMark += read; // update the high-water mark } } finally { _tempFileMutex.ReleaseMutex(); } } else { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.ReadCallBack() - read complete - EndRead() returned zero"); #endif // set Length if not already done so if (_fullStreamLength < 0) _fullStreamLength = _highWaterMark; } // all done? if (_fullStreamLength == _highWaterMark) { // prevent further requests _fullDownloadComplete = true; } } finally { // Set the ManualResetEvent if (!_disposed && _readEventHandles[(int)ReadEvent.FullDownloadReadEvent] != null) _readEventHandles[(int)ReadEvent.FullDownloadReadEvent].Set(); } } return; } #endregion #region ByteRangeRequest ////// Ensure ByteRangeDownloader is created and available /// ////// Critical /// 1) accesses Critical collection _readEventHandles /// 2) local assert of WebPermission to access get_Proxy property /// 3) accesses Critical member _originalRequest /// 4) assigns Critical property ByteRangeDownloader.Proxy /// Safe /// 1) _readEventHandles is Critical for set /// 2) WebPermission assert is local and needed only to synchronize two WebRequest properties /// 3) _originalRequest.get_Proxy is safe because Proxy is known safe /// (and Proxy is only Critical member of _originalRequest) /// 4) ByteRangeDownloader.Proxy set is safe because the _originalRequest.Proxy is safe /// [SecurityCritical, SecurityTreatAsSafe] private void EnsureDownloader() { if (_byteRangeDownloader == null) { _byteRangeDownloader = new ByteRangeDownloader(_uri, _tempFileStream, _readEventHandles[(int)ReadEvent.ByteRangeReadEvent].SafeWaitHandle, _tempFileMutex); // Local assert to allow Proxy get/set under partial trust new WebPermission(PermissionState.Unrestricted).Assert(); // Blessed try { _byteRangeDownloader.Proxy = _originalRequest.Proxy; } finally { WebPermission.RevertAssert(); } _byteRangeDownloader.Credentials = _originalRequest.Credentials; _byteRangeDownloader.CachePolicy = _originalRequest.CachePolicy; _byteRangesAvailable = new ArrayList(); // byte ranges that are downloaded } } ////// MakeByteRangeRequest /// ///helper method to reduce complexity in GetData(). private void MakeByteRangeRequest(Block block) { // Currently HttpWebRequest.AddRange can only handle int while the offset of stream can be long // we should not make additional webrequest in that case // block.Offset > Int32.MaxValue // block.Offset + block.Length - 1 > Int32.MaxValue // No need to do "checked" since block.Length > 0 && block.Length <= Int32.MaxValue if (block.Offset > (Int32.MaxValue - block.Length + 1)) return; // spawn a request EnsureDownloader(); // make it worth the trouble - pad out to some reasonable size if (block.Length < _additionalRequestMinSize) { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.MakeByteRangeRequest() offset:{0} length:{1} (padded to {2})", block.Offset, block.Length, _additionalRequestMinSize); #endif block.Length = _additionalRequestMinSize; } #if DEBUG else { if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.MakeByteRangeRequest() offset:{0} length:{1}", block.Offset, block.Length); } #endif // don't ask for more than the stream can accomodate TrimBlockToStreamLength(block); checked { // don't ask if there is no data to ask for if (block.Length > 0) { // request the data int[,] ranges = new int[1, 2]; ranges[0, 0] = (int)block.Offset; ranges[0, 1] = block.Length; _byteRangeDownloader.RequestByteRanges(ranges); _inAdditionalRequest = true; // only do these one at a time } } } ////// GetByteRangeData /// ///PRECONDITION: lock (_syncObject). /// Side effects of updating _byteRangesAvailable and _inAdditionalRequest. private void GetByteRangeData() { int[,] ranges; // query the ByteRangeDownloader for the details ranges = _byteRangeDownloader.GetDownloadedByteRanges(); if (ranges.GetLength(0) > 0) { // Add our "fullDownload" range just in case we can satisfy a request that straddles the // boundary between the highWaterMark and a byte range. // We can just "blindly" add this every time because the merging code will keep the // growth from getting out of control. _byteRangesAvailable.Insert(0, new Block(0, (int)_highWaterMark)); // add to our collection of previously downloaded ranges int r = 0; // index into ranges while (r < ranges.GetLength(0)) { _byteRangesAvailable.Add(new Block(ranges[r,0], ranges[r,1])); r++; #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) _unmergedBlocks++; // statistics on merge performance #endif } // sort them _byteRangesAvailable.Sort(); // must sort before merging // merge them MergeByteRanges(_byteRangesAvailable); #if DEBUG // Note that this includes the "fullDownload" range if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.GetData() total byteranges:{0} after merging:{1}", _unmergedBlocks, _byteRangesAvailable.Count); #endif _inAdditionalRequest = false; // allow more byte-range requests } } ////// IsByteRangeAvailable /// /// query ///number of bytes that are available starting at the beginning of the given block private int BytesInByteRangeAvailable(Block block) { int bytesAvailable = 0; // we can be called even if the byteRangeDownloader is not in use if (_byteRangesAvailable != null) { checked { // handle "over the end requests" which we truncate automatically when making the actual request Debug.Assert(_fullStreamLength >= 0, "We assume _fullStreamLength is correct for Http cases - only Ftp can return bogus values"); TrimBlockToStreamLength(block); // now search - could be replaced with BinarySearch as list is ordered by offset foreach (Block data in _byteRangesAvailable) { // we need the bytes to start from the beginning because that's the // only type of partial response we can give if ((data.Offset <= block.Offset) && (data.End > block.Offset)) bytesAvailable = Math.Min(block.Length, (int)(data.End - block.Offset)); // if we have some data, or we are beyond any possibility of a match then exit if (bytesAvailable > 0 || data.Offset >= block.End) break; } } } return bytesAvailable; } ////// TrimByteRangeRequest - reduce the request to eliminate request for existing data /// /// requested block ///bytes currently available at the start of the original request block ///We currently ignore the case where our request entirely contains an existing data block because /// there is no support for non-contiguous byte-range requests. If such capability is introduced, we might /// revisit this logic and split the request into two requests that don't coincide with the existing data. private int TrimByteRangeRequest(Block block) { int bytesAvailable = 0; // we can be called even if the byteRangeDownloader is not in use if (_byteRangesAvailable != null) { checked { // search through sorted list - move to BinarySearch if we predict huge number of entries foreach (Block data in _byteRangesAvailable) { // Exit early when we know we cannot possibly have a match. // We know this when the current data block offset is beyond the end of our request. if (block.End <= data.Offset) break; // check for Head intersection (or complete co-incidence) if ((block.Offset >= data.Offset) && (data.End > block.Offset)) { // completely satisfies? if (block.End <= data.End) bytesAvailable = block.Length; else { bytesAvailable = (int)(data.End - block.Offset); block.Offset = data.End; } block.Length -= bytesAvailable; } // check for Tail intersection (but not request extending beyond data block as this would split the request) if ((block.Offset <= data.Offset) && (block.End > data.Offset) && (block.End <= data.End)) { block.Length = (int)(data.Offset - block.Offset); } if (bytesAvailable > 0) break; } // zero length block is possible if request is a perfect match } } return bytesAvailable; } ////// Stream Block /// ///represents a byte range that has been downloaded and is available private class Block: IComparable { internal Block(long offset, int length) { Debug.Assert(offset >= 0); Debug.Assert(length >= 0); _offset = offset; _length = length; } // the index of the byte after the last byte in the block - useful for calculations internal long End { get { checked { return _offset + _length; } } } internal long Offset { get { return _offset; } set { Debug.Assert(value >= 0); _offset = value; } } internal int Length { get { return _length; } set { Debug.Assert(value >= 0); _length = value; } } // this allows Sort() int IComparable.CompareTo(object x) { // sort by offset Block b = (Block)x; if (_offset < b._offset) return -1; if (_offset > b._offset) return 1; // offsets are equal so now the shortest one goes first if (_length == b._length) return 0; if (_length < b._length) return -1; // _length > b._length return 1; } // returns true if these two blocks overlap or are contiguous // assumes _offset <= b._offset because they are supposed to be sorted internal bool Mergeable(Block b) { checked { if (_offset <= b._offset) return (_offset + _length - b._offset >= 0); else return (b._offset + b._length - _offset >= 0); } } // combine two blocks that overlap or are adjacent internal void Merge(Block b) { checked { Debug.Assert(_offset <= b._offset); Debug.Assert(Mergeable(b)); _length = (int)(Math.Max(_offset + _length, b._offset + b._length) - _offset); } } private long _offset; // zero-based index from start of stream private int _length; // number of bytes starting at _offset }; // Merge all overlapping and adjacent ranges // This function assumes the list of ranges are already sorted // Function is destructive (in-place) private void MergeByteRanges(ArrayList ranges) { checked { // For each byte range for (int i = 0; i + 1 < ranges.Count; i++) { Block b = (Block)ranges[i]; // handle possible multiple-overlap (or adjacency) while (b.Mergeable((Block)ranges[i + 1])) { b.Merge((Block)ranges[i + 1]); ranges.RemoveAt(i + 1); // don't index off the end of the list if (i + 1 >= ranges.Count) break; } } } } #endregion ////// ByteRange event was fired /// /// current request ///data known available ///pre-condition - SyncLock must be acquired private int HandleByteRangeReadEvent(Block block) { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.GetData() - byteRange data Event signaled"); #endif Debug.Assert(block.Length > 0); int bytesAvailable = 0; checked { // We want the "full range test" at first because we don't want to tweak our heuristic unless // we really underestimated. If not all data is available, we take a more relaxed result // in the "else" clause below. if (_highWaterMark > block.Offset) bytesAvailable = (int)Math.Min(block.Length, _highWaterMark - block.Offset); // maybe our request can be satisfied without the byte-range? if (bytesAvailable == block.Length) { // network traffic is flowing better than expected - increase the threshold _additionalRequestThreshold *= 2; #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.GetData() - byteRange request satisfied by full download - increasing threshold"); #endif } else { // query the byte-range object for the new range if (!_byteRangeDownloader.ErroredOut) { // update our local list from the ByteRangeDownloader GetByteRangeData(); // determine if the ByteRangeDownloader provided any of the data we need bytesAvailable = BytesInByteRangeAvailable(block); } else { // prevent future attempts if downloader has had trouble (could be HTTP server that does not support 1.1 protocol) _allowByteRangeRequests = false; } } } return bytesAvailable; } ////// FullDownload event was fired /// /// current request ///true if ANY data is available ///pre-condition - SyncLock must be acquired private int HandleFullDownloadReadEvent(Block block) { int dataAvailable = 0; if (_fullDownloadComplete) { TrimBlockToStreamLength(block); dataAvailable = block.Length; } else { checked { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.GetData() - Request Data (BeginRead)"); #endif // Continue reading data until // responseStream.EndRead exhausts the stream _responseStream.BeginRead(_readBuf, 0, _readBuf.Length, new AsyncCallback(ReadCallBack), this); // any data is reason to return true if (_highWaterMark > block.Offset) dataAvailable = (int)Math.Min(block.Length, _highWaterMark - block.Offset); } } return dataAvailable; } ////// Get data, blocking until at least one byte is available /// /// current request ///bytes available ///Attempts to obtain the data from the temp file. Spawns a ByteRange /// request if enabled and appropriate. Returns when any data is available or /// the request exceeded the actual stream length and the entire stream is available. ////// Critical /// 1) accesses Critical collection _readEventHandles /// Safe /// 1) _readEventHandles is Critical for set /// [SecurityCritical, SecurityTreatAsSafe] private int GetData(Block block) { TrimBlockToStreamLength(block); if (block.Length == 0) return 0; int dataAvailable = 0; // no point in waiting if all data is available while (dataAvailable == 0) { Debug.Assert(block.Length > 0); lock (_syncObject) { if (_highWaterMark > block.Offset) { dataAvailable = (int)Math.Min(block.Length, _highWaterMark - block.Offset); } else { // Check for overlap with existing data - do this even if we are currently in a byte-range request dataAvailable = TrimByteRangeRequest(block); // Should we spawn a byte-range request? // All Criteria must be met: // 1. _allowByteRangeRequests - protocol is http and we know the full stream length // 2. !_inAdditionalRequest - there is no outstanding request - we currently only support one at a time // 3. block.Offset > _highWaterMark + _additionalRequestThreshold - heuristic that says it's "worth it" to spawn a separate request // 4. ((_byteRangeDownloader == null) || !_byteRangeDownloader.ErroredOut) - either there is no // existing ByteRangeDownloader (this is our first byte-range request), or the downloader is non-null and has not Errored out. // 5. The block we were asked to retrieve was not satisfied by existing data if (_allowByteRangeRequests && !_inAdditionalRequest && (_highWaterMark <= Int64.MaxValue - (long) _additionalRequestThreshold) // Ensure that we don't get overflow from the next line && (block.Offset > _highWaterMark + (long) _additionalRequestThreshold) && ((_byteRangeDownloader == null) || !_byteRangeDownloader.ErroredOut) && (block.Length > 0)) { MakeByteRangeRequest(block); // request data } } } // We were unable to satisfy the request so we must wait for either the main download thread to signal // that new data is available, or the byte-range downloader to signal that new data is available. if (dataAvailable == 0) { #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.GetData() - wait start"); // for debugging deadlock #endif // WaitAny if both events are in use - or just ReadEvent eventFired; if (_allowByteRangeRequests) { // either way, we must wait for data either from the full-file request or any spawned byte-range request int index = WaitHandle.WaitAny(_readEventHandles); if (index > 128) // handle +128 case - see SDK index -= 128; eventFired = (ReadEvent)index; } else { // no byte-range downloader in use - wait only for the fulldownload event eventFired = ReadEvent.FullDownloadReadEvent; _readEventHandles[(int)eventFired].WaitOne(); } #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.GetData() - wait end [{0}]", eventFired); #endif lock (_syncObject) { // if byteRange we need to keep track of what data is now available if (eventFired == ReadEvent.ByteRangeReadEvent) { dataAvailable = HandleByteRangeReadEvent(block); } else // FullDownloadReadEvent { dataAvailable = HandleFullDownloadReadEvent(block); // break regardless of if we satisfied the request because no more data is forthcoming if (_fullDownloadComplete) { ReleaseFullDownloadResources(); break; } } } } } #if DEBUG if (System.IO.Packaging.PackWebRequestFactory._traceSwitch.Enabled) System.Diagnostics.Trace.TraceInformation("NetStream.GetData() satisfied with {0} bytes", dataAvailable); #endif // could exit with dataAvailable == 0 if we didn't know the full stream length coming in and the request // was beyond the actual stream length return dataAvailable; } ////// Restricts the block length so that it does not extend beyond the end of the stream /// /// block to inspect and possibly modify ///has no effect if full stream length unknown private void TrimBlockToStreamLength(Block block) { checked { // trim to be sure we don't say we have more bytes than the stream holds if (_fullStreamLength >= 0) block.Length = (int)Math.Min(block.Length, _fullStreamLength - block.Offset); } } ////// Release resources only needed for fulldownload /// ////// Critical /// 1) modifies Critical collection _readEventHandles /// [SecurityCritical] private void ReleaseFullDownloadResources() { Debug.Assert(_fullDownloadComplete, "Do not call this unless full download is complete."); // ignore logic errors - only do this once if (_readBuf != null) { // don't need these anymore _byteRangesAvailable = null; _readBuf = null; try { try { FreeByteRangeDownloader(); // release the full download read event as it is no longer needed if (_readEventHandles[(int)ReadEvent.FullDownloadReadEvent] != null) { _readEventHandles[(int)ReadEvent.FullDownloadReadEvent].Close(); _readEventHandles[(int)ReadEvent.FullDownloadReadEvent] = null; } } finally { // FreeFullDownload if (_responseStream != null) { _responseStream.Close(); } } } finally { _responseStream = null; } } } ////// Free ByteRangeDownloader if it is allocated /// ////// Critical /// 1) modifies Critical collection _readEventHandles /// [SecurityCritical] private void FreeByteRangeDownloader() { if (_byteRangeDownloader != null) { try { ((IDisposable)_byteRangeDownloader).Dispose(); if (_readEventHandles[(int)ReadEvent.ByteRangeReadEvent] != null) { _readEventHandles[(int)ReadEvent.ByteRangeReadEvent].Close(); _readEventHandles[(int)ReadEvent.ByteRangeReadEvent] = null; } } finally { _byteRangeDownloader = null; } } } ////// FreeTempFile - frees resources related to the tempfile /// private void FreeTempFile() { // Stream and Mutex bool mutexObtained = false; Invariant.Assert(_tempFileStream != null); try { mutexObtained = _tempFileMutex.WaitOne(_tempFileSyncTimeout, false); // wait up to 5 seconds _tempFileStream.Close(); } finally { // only release it if we own it if (mutexObtained) { // make sure this is released even if there is a stream error _tempFileMutex.ReleaseMutex(); // only close this if we obtained it // let the garbage collector get it eventually if we didn't _tempFileMutex.Close(); // does not throw an exception } _tempFileStream = null; _tempFileName = null; _tempFileMutex = null; } } //----------------------------------------------------- // // Private Properties // //------------------------------------------------------ //----------------------------------------------------- // // Private Fields // //------------------------------------------------------ private enum ReadEvent { FullDownloadReadEvent = 0, ByteRangeReadEvent = 1, MaxReadEventEnum }; Uri _uri; // uri we are resolving ////// Critical /// 1) Proxy member is Critical because we use it under Unrestricted assert /// [SecurityCritical] WebRequest _originalRequest; // Proxy member is Critical Stream _tempFileStream; // local temp stream we are writing to and reading from - protected by _tempFileMutex long _position; // our "logical stream position" // syncObject - provides mutually-exclusive access control to the following entities: // 1. _highWaterMark - this is actually queried outside of a lock in get_Length, but this is safe as a stale value only impacts perf // Does not fully protect the following entities: // 1. _disposed - Not in all cases - CheckDisposed(), CanRead, CanSeek do not lock first but we are not "threadsafe" and _disposed is only // modified in the Dispose() call. The only other thread that can inspect it does so in ReadCallBack - both places // we lock on _syncObject so this is safe. // 2. _responseStream - yes // 3. _readEventHandles - cannot be as these are synchronization objects which must be freely accessible // 4. _byteRangeDownloader - yes except this object can be disposed while it is still "active" - it is expected to behave correctly in this // scenario. private Object _syncObject = new Object(); private volatile bool _disposed; // full-file download private const int _readTimeOut = 40000; // how long before we give-up on async Read? (milliseconds) private const int _additionalRequestMinSize = 0x1000; // minimum size for a ByteRangeRequest - make it worth the trouble (overhead) private const int _bufferSize = 0x1000; // smaller allows for quicker response private const int _tempFileSyncTimeout = 5000; // wait 5 seconds for byteRangeDownloader to release it's File mutex before closing it private uint _additionalRequestThreshold = 0x4000; // dynamically adjusting this value based on network conditions (start small because this only goes up) private Stream _responseStream; // Stream returned by WebResponse private byte[] _readBuf; // destination buffer for async inner webResponse reads private string _tempFileName; // file name of temp file private long _fullStreamLength; // need to return this in call to get_Length private volatile bool _fullDownloadComplete; // download complete if this is true - prevents us from waiting for more data // this is volatile because it can be updated and inspected by different threads private long _highWaterMark; // how much data is currently available from full download // used to determine whether it makes sense to spawn a byte-range download // access to this value must be synchronized using lock() // OS synchronization event used to signal that new data is available [SecurityCritical] private EventWaitHandle[] _readEventHandles = new EventWaitHandle[(int)ReadEvent.MaxReadEventEnum]; // protects the _tempFileStream object and allows both our thread and the ByteRangeDownloader thread to safely access the temp stream private Mutex _tempFileMutex = new Mutex(false); // byte-range downloads private bool _allowByteRangeRequests; // toggle private ByteRangeDownloader _byteRangeDownloader; // handles byte-range downloads for us private bool _inAdditionalRequest; // only spawn one byte-range request at a time private ArrayList _byteRangesAvailable; // byte ranges that are downloaded #if DEBUG private int _unmergedBlocks; // for trace only #endif } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- FileDetails.cs
- ClientProxyGenerator.cs
- BindUriHelper.cs
- AppearanceEditorPart.cs
- CheckBoxRenderer.cs
- SecondaryIndexDefinition.cs
- XmlProcessingInstruction.cs
- MediaPlayerState.cs
- CallTemplateAction.cs
- DataViewSettingCollection.cs
- AffineTransform3D.cs
- TagNameToTypeMapper.cs
- WebDisplayNameAttribute.cs
- FontFamilyValueSerializer.cs
- Lasso.cs
- Calendar.cs
- CorrelationResolver.cs
- CompiledAction.cs
- ConfigurationElement.cs
- XmlSchemaSimpleTypeRestriction.cs
- TextWriter.cs
- Hex.cs
- AssemblyBuilderData.cs
- ComponentSerializationService.cs
- SimpleApplicationHost.cs
- RelationshipConverter.cs
- NotifyInputEventArgs.cs
- ProfileManager.cs
- ObjectDataSourceView.cs
- InnerItemCollectionView.cs
- ConfigXmlCDataSection.cs
- Italic.cs
- XmlSchemaGroup.cs
- SerializationEventsCache.cs
- ServiceNameCollection.cs
- StylusPointCollection.cs
- EventlogProvider.cs
- PageThemeParser.cs
- DataGridViewSelectedColumnCollection.cs
- ToolStripManager.cs
- TextRangeAdaptor.cs
- AssociationTypeEmitter.cs
- InvokeHandlers.cs
- ChildTable.cs
- TaskFileService.cs
- IDReferencePropertyAttribute.cs
- TextSelectionProcessor.cs
- XmlBaseWriter.cs
- ToolStripPanelCell.cs
- RuleValidation.cs
- HtmlContainerControl.cs
- MediaSystem.cs
- SoundPlayerAction.cs
- DefaultEventAttribute.cs
- ParameterBuilder.cs
- Wrapper.cs
- IISMapPath.cs
- Dictionary.cs
- DATA_BLOB.cs
- WrapPanel.cs
- ObjectDataSourceView.cs
- TargetControlTypeCache.cs
- CellNormalizer.cs
- FrameworkElement.cs
- TreeView.cs
- DateTimeConverter2.cs
- PermissionSetTriple.cs
- SwitchElementsCollection.cs
- TypedTableBaseExtensions.cs
- ExtendedProtectionPolicy.cs
- TimeoutHelper.cs
- SqlReferenceCollection.cs
- Events.cs
- SqlCommandSet.cs
- ContractComponent.cs
- ActivityExecutorSurrogate.cs
- FileLoadException.cs
- LinearQuaternionKeyFrame.cs
- SmiEventSink_DeferedProcessing.cs
- ProjectionRewriter.cs
- GenericAuthenticationEventArgs.cs
- DoubleLinkListEnumerator.cs
- ReliableMessagingHelpers.cs
- PanelDesigner.cs
- RadioButtonStandardAdapter.cs
- ResourceManagerWrapper.cs
- CryptoApi.cs
- TypeBrowser.xaml.cs
- WebPartRestoreVerb.cs
- KeyPullup.cs
- VirtualPathUtility.cs
- TextServicesLoader.cs
- PageContent.cs
- RegexParser.cs
- SingleAnimationUsingKeyFrames.cs
- ActivityExecutionContextCollection.cs
- StyleTypedPropertyAttribute.cs
- RoutedEventHandlerInfo.cs
- ConnectionStringsExpressionBuilder.cs
- Vector3DCollectionValueSerializer.cs