PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Languages/IronPython/IronPython.Modules/bz2/dotnetzip/BZip2/ParallelBZip2OutputStream.cs

http://github.com/IronLanguages/main
C# | 991 lines | 482 code | 109 blank | 400 comment | 67 complexity | b7c820502280bccb5b6712b4d7e4f9e0 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. //#define Trace
  2. // ParallelBZip2OutputStream.cs
  3. // ------------------------------------------------------------------
  4. //
  5. // Copyright (c) 2011 Dino Chiesa.
  6. // All rights reserved.
  7. //
  8. // This code module is part of DotNetZip, a zipfile class library.
  9. //
  10. // ------------------------------------------------------------------
  11. //
  12. // This code is licensed under the Microsoft Public License.
  13. // See the file License.txt for the license details.
  14. // More info on: http://dotnetzip.codeplex.com
  15. //
  16. // ------------------------------------------------------------------
  17. //
  18. // Last Saved: <2011-August-02 16:44:24>
  19. //
  20. // ------------------------------------------------------------------
  21. //
  22. // This module defines the ParallelBZip2OutputStream class, which is a
  23. // BZip2 compressing stream. This code was derived in part from Apache
  24. // commons source code. The license below applies to the original Apache
  25. // code.
  26. //
  27. // ------------------------------------------------------------------
  28. // flymake: csc.exe /t:module BZip2InputStream.cs BZip2Compressor.cs Rand.cs BCRC32.cs @@FILE@@
  29. /*
  30. * Licensed to the Apache Software Foundation (ASF) under one
  31. * or more contributor license agreements. See the NOTICE file
  32. * distributed with this work for additional information
  33. * regarding copyright ownership. The ASF licenses this file
  34. * to you under the Apache License, Version 2.0 (the
  35. * "License"); you may not use this file except in compliance
  36. * with the License. You may obtain a copy of the License at
  37. *
  38. * http://www.apache.org/licenses/LICENSE-2.0
  39. *
  40. * Unless required by applicable law or agreed to in writing,
  41. * software distributed under the License is distributed on an
  42. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  43. * KIND, either express or implied. See the License for the
  44. * specific language governing permissions and limitations
  45. * under the License.
  46. */
  47. // Design Notes:
  48. //
  49. // This class follows the classic Decorator pattern: it is a Stream that
  50. // wraps itself around a Stream, and in doing so provides bzip2
  51. // compression as callers Write into it. It is exactly the same in
  52. // outward function as the BZip2OutputStream, except that this class can
  53. // perform compression using multiple independent threads. Because of
  54. // that, and because of the CPU-intensive nature of BZip2 compression,
  55. // this class can perform significantly better (in terms of wall-click
  56. // time) than the single-threaded variant, at the expense of memory and
  57. // CPU utilization.
  58. //
  59. // BZip2 is a straightforward data format: there are 4 magic bytes at
  60. // the top of the file, followed by 1 or more compressed blocks. There
  61. // is a small "magic byte" trailer after all compressed blocks.
  62. //
  63. // In concept parallelizing BZip2 is simple: do the CPU-intensive
  64. // compression for each block in a separate thread, then emit the
  65. // compressed output, in order, to the output stream. Each block can be
  66. // compressed independently, so a block is the natural candidate for the
  67. // parcel of work that can be passed to an independent worker thread.
  68. //
  69. // The design approach used here is simple: within the Write() method of
  70. // the stream, fill a block. When the block is full, pass it to a
  71. // background worker thread for compression. When the compressor thread
  72. // completes its work, the main thread (the application thread that
  73. // calls Write()) can send the compressed data to the output stream,
  74. // being careful to respect the order of the compressed blocks.
  75. //
  76. // The challenge of ordering the compressed data is a solved and
  77. // well-understood problem - it is the same approach here as DotNetZip
  78. // uses in the ParallelDeflateOutputStream. It is a map/reduce approach
  79. // in design intent.
  80. //
  81. // One new twist for BZip2 is that the compressor output is not
  82. // byte-aligned. In other words the final output of a compressed block
  83. // will in general be a number of bits that is not a multiple of
  84. // 8. Therefore, combining the ordered results of the N compressor
  85. // threads requires additional byte-shredding by the parent
  86. // stream. Hence this stream uses a BitWriter to adapt bit-oriented
  87. // BZip2 output to the byte-oriented .NET Stream.
  88. //
  89. // The approach used here creates N instances of the BZip2Compressor
  90. // type, where N is governed by the number of cores (cpus) and limited
  91. // by the MaxWorkers property exposed by this class. Each
  92. // BZip2Compressor instance gets its own MemoryStream, to which it
  93. // writes its data, via a BitWriter.
  94. //
  95. // along with the bit accumulator described above. The MemoryStream
  96. // would gather the byte-aligned compressed output of the compressor.
  97. // When reducing the output of the various workers, this class must
  98. // again do the byte-shredding thing. The data from the compressors is
  99. // therefore shredded twice: once when being placed into the
  100. // MemoryStream, and again when emitted into the final output stream
  101. // that this class decorates. This is an unfortunate and seemingly
  102. // unavoidable inefficiency. Two rounds of byte-shredding will use more
  103. // CPU than we'd like, but I haven't imagined a way to avoid it.
  104. //
  105. // The BZip2Compressor is designed to write directly into the parent
  106. // stream's accumulator (BitWriter) when possible, and write into a
  107. // distinct BitWriter when necessary. The former can be used in a
  108. // single-thread scenario, while the latter is required in a
  109. // multi-thread scenario.
  110. //
  111. // ----
  112. //
  113. // Regarding the Apache code base: Most of the code in this particular
  114. // class is related to stream operations and thread synchronization, and
  115. // is my own code. It largely does not rely on any code obtained from
  116. // Apache commons. If you compare this code with the Apache commons
  117. // BZip2OutputStream, you will see very little code that is common,
  118. // except for the nearly-boilerplate structure that is common to all
  119. // subtypes of System.IO.Stream. There may be some small remnants of
  120. // code in this module derived from the Apache stuff, which is why I
  121. // left the license in here. Most of the Apache commons compressor magic
  122. // has been ported into the BZip2Compressor class.
  123. //
  124. using System;
  125. using System.IO;
  126. using System.Collections.Generic;
  127. using System.Threading;
  128. namespace Ionic.BZip2
  129. {
  130. internal class WorkItem
  131. {
  132. public int index;
  133. public BZip2Compressor Compressor { get; private set; }
  134. public MemoryStream ms;
  135. public int ordinal;
  136. public BitWriter bw;
  137. public WorkItem(int ix, int blockSize)
  138. {
  139. // compressed data gets written to a MemoryStream
  140. this.ms = new MemoryStream();
  141. this.bw = new BitWriter(ms);
  142. this.Compressor = new BZip2Compressor(bw, blockSize);
  143. this.index = ix;
  144. }
  145. }
  146. /// <summary>
  147. /// A write-only decorator stream that compresses data as it is
  148. /// written using the BZip2 algorithm. This stream compresses by
  149. /// block using multiple threads.
  150. /// </summary>
  151. /// <para>
  152. /// This class performs BZIP2 compression through writing. For
  153. /// more information on the BZIP2 algorithm, see
  154. /// <see href="http://en.wikipedia.org/wiki/BZIP2"/>.
  155. /// </para>
  156. ///
  157. /// <para>
  158. /// This class is similar to <see cref="Ionic.BZip2.BZip2OutputStream"/>,
  159. /// except that this implementation uses an approach that employs multiple
  160. /// worker threads to perform the compression. On a multi-cpu or multi-core
  161. /// computer, the performance of this class can be significantly higher than
  162. /// the single-threaded BZip2OutputStream, particularly for larger streams.
  163. /// How large? Anything over 10mb is a good candidate for parallel
  164. /// compression.
  165. /// </para>
  166. ///
  167. /// <para>
  168. /// The tradeoff is that this class uses more memory and more CPU than the
  169. /// vanilla <c>BZip2OutputStream</c>. Also, for small files, the
  170. /// <c>ParallelBZip2OutputStream</c> can be much slower than the vanilla
  171. /// <c>BZip2OutputStream</c>, because of the overhead associated to using the
  172. /// thread pool.
  173. /// </para>
  174. ///
  175. /// <seealso cref="Ionic.BZip2.BZip2OutputStream" />
  176. public class ParallelBZip2OutputStream : System.IO.Stream
  177. {
  178. private static readonly int BufferPairsPerCore = 4;
  179. private int _maxWorkers;
  180. private bool firstWriteDone;
  181. private int lastFilled;
  182. private int lastWritten;
  183. private int latestCompressed;
  184. private int currentlyFilling;
  185. private volatile Exception pendingException;
  186. private bool handlingException;
  187. private bool emitting;
  188. private System.Collections.Generic.Queue<int> toWrite;
  189. private System.Collections.Generic.Queue<int> toFill;
  190. private System.Collections.Generic.List<WorkItem> pool;
  191. private object latestLock = new object();
  192. private object eLock = new object(); // for exceptions
  193. private object outputLock = new object(); // for multi-thread output
  194. private AutoResetEvent newlyCompressedBlob;
  195. long totalBytesWrittenIn;
  196. long totalBytesWrittenOut;
  197. bool leaveOpen;
  198. uint combinedCRC;
  199. Stream output;
  200. BitWriter bw;
  201. int blockSize100k; // 0...9
  202. private TraceBits desiredTrace = TraceBits.Crc | TraceBits.Write;
  203. /// <summary>
  204. /// Constructs a new <c>ParallelBZip2OutputStream</c>, that sends its
  205. /// compressed output to the given output stream.
  206. /// </summary>
  207. ///
  208. /// <param name='output'>
  209. /// The destination stream, to which compressed output will be sent.
  210. /// </param>
  211. ///
  212. /// <example>
  213. ///
  214. /// This example reads a file, then compresses it with bzip2 file,
  215. /// and writes the compressed data into a newly created file.
  216. ///
  217. /// <code>
  218. /// var fname = "logfile.log";
  219. /// using (var fs = File.OpenRead(fname))
  220. /// {
  221. /// var outFname = fname + ".bz2";
  222. /// using (var output = File.Create(outFname))
  223. /// {
  224. /// using (var compressor = new Ionic.BZip2.ParallelBZip2OutputStream(output))
  225. /// {
  226. /// byte[] buffer = new byte[2048];
  227. /// int n;
  228. /// while ((n = fs.Read(buffer, 0, buffer.Length)) > 0)
  229. /// {
  230. /// compressor.Write(buffer, 0, n);
  231. /// }
  232. /// }
  233. /// }
  234. /// }
  235. /// </code>
  236. /// </example>
  237. public ParallelBZip2OutputStream(Stream output)
  238. : this(output, BZip2.MaxBlockSize, false)
  239. {
  240. }
  241. /// <summary>
  242. /// Constructs a new <c>ParallelBZip2OutputStream</c> with specified blocksize.
  243. /// </summary>
  244. /// <param name = "output">the destination stream.</param>
  245. /// <param name = "blockSize">
  246. /// The blockSize in units of 100000 bytes.
  247. /// The valid range is 1..9.
  248. /// </param>
  249. public ParallelBZip2OutputStream(Stream output, int blockSize)
  250. : this(output, blockSize, false)
  251. {
  252. }
  253. /// <summary>
  254. /// Constructs a new <c>ParallelBZip2OutputStream</c>.
  255. /// </summary>
  256. /// <param name = "output">the destination stream.</param>
  257. /// <param name = "leaveOpen">
  258. /// whether to leave the captive stream open upon closing this stream.
  259. /// </param>
  260. public ParallelBZip2OutputStream(Stream output, bool leaveOpen)
  261. : this(output, BZip2.MaxBlockSize, leaveOpen)
  262. {
  263. }
  264. /// <summary>
  265. /// Constructs a new <c>ParallelBZip2OutputStream</c> with specified blocksize,
  266. /// and explicitly specifies whether to leave the wrapped stream open.
  267. /// </summary>
  268. ///
  269. /// <param name = "output">the destination stream.</param>
  270. /// <param name = "blockSize">
  271. /// The blockSize in units of 100000 bytes.
  272. /// The valid range is 1..9.
  273. /// </param>
  274. /// <param name = "leaveOpen">
  275. /// whether to leave the captive stream open upon closing this stream.
  276. /// </param>
  277. public ParallelBZip2OutputStream(Stream output, int blockSize, bool leaveOpen)
  278. {
  279. if (blockSize < BZip2.MinBlockSize || blockSize > BZip2.MaxBlockSize)
  280. {
  281. var msg = String.Format("blockSize={0} is out of range; must be between {1} and {2}",
  282. blockSize,
  283. BZip2.MinBlockSize, BZip2.MaxBlockSize);
  284. throw new ArgumentException(msg, "blockSize");
  285. }
  286. this.output = output;
  287. if (!this.output.CanWrite)
  288. throw new ArgumentException("The stream is not writable.", "output");
  289. this.bw = new BitWriter(this.output);
  290. this.blockSize100k = blockSize;
  291. this.leaveOpen = leaveOpen;
  292. this.combinedCRC = 0;
  293. this.MaxWorkers = 16; // default
  294. EmitHeader();
  295. }
  296. private void InitializePoolOfWorkItems()
  297. {
  298. this.toWrite = new Queue<int>();
  299. this.toFill = new Queue<int>();
  300. this.pool = new System.Collections.Generic.List<WorkItem>();
  301. int nWorkers = BufferPairsPerCore * Environment.ProcessorCount;
  302. nWorkers = Math.Min(nWorkers, this.MaxWorkers);
  303. for(int i=0; i < nWorkers; i++)
  304. {
  305. this.pool.Add(new WorkItem(i, this.blockSize100k));
  306. this.toFill.Enqueue(i);
  307. }
  308. this.newlyCompressedBlob = new AutoResetEvent(false);
  309. this.currentlyFilling = -1;
  310. this.lastFilled = -1;
  311. this.lastWritten = -1;
  312. this.latestCompressed = -1;
  313. }
  314. /// <summary>
  315. /// The maximum number of concurrent compression worker threads to use.
  316. /// </summary>
  317. ///
  318. /// <remarks>
  319. /// <para>
  320. /// This property sets an upper limit on the number of concurrent worker
  321. /// threads to employ for compression. The implementation of this stream
  322. /// employs multiple threads from the .NET thread pool, via
  323. /// ThreadPool.QueueUserWorkItem(), to compress the incoming data by
  324. /// block. As each block of data is compressed, this stream re-orders the
  325. /// compressed blocks and writes them to the output stream.
  326. /// </para>
  327. ///
  328. /// <para>
  329. /// A higher number of workers enables a higher degree of
  330. /// parallelism, which tends to increase the speed of compression on
  331. /// multi-cpu computers. On the other hand, a higher number of buffer
  332. /// pairs also implies a larger memory consumption, more active worker
  333. /// threads, and a higher cpu utilization for any compression. This
  334. /// property enables the application to limit its memory consumption and
  335. /// CPU utilization behavior depending on requirements.
  336. /// </para>
  337. ///
  338. /// <para>
  339. /// By default, DotNetZip allocates 4 workers per CPU core, subject to the
  340. /// upper limit specified in this property. For example, suppose the
  341. /// application sets this property to 16. Then, on a machine with 2
  342. /// cores, DotNetZip will use 8 workers; that number does not exceed the
  343. /// upper limit specified by this property, so the actual number of
  344. /// workers used will be 4 * 2 = 8. On a machine with 4 cores, DotNetZip
  345. /// will use 16 workers; again, the limit does not apply. On a machine
  346. /// with 8 cores, DotNetZip will use 16 workers, because of the limit.
  347. /// </para>
  348. ///
  349. /// <para>
  350. /// For each compression "worker thread" that occurs in parallel, there is
  351. /// up to 2mb of memory allocated, for buffering and processing. The
  352. /// actual number depends on the <see cref="BlockSize"/> property.
  353. /// </para>
  354. ///
  355. /// <para>
  356. /// CPU utilization will also go up with additional workers, because a
  357. /// larger number of buffer pairs allows a larger number of background
  358. /// threads to compress in parallel. If you find that parallel
  359. /// compression is consuming too much memory or CPU, you can adjust this
  360. /// value downward.
  361. /// </para>
  362. ///
  363. /// <para>
  364. /// The default value is 16. Different values may deliver better or
  365. /// worse results, depending on your priorities and the dynamic
  366. /// performance characteristics of your storage and compute resources.
  367. /// </para>
  368. ///
  369. /// <para>
  370. /// The application can set this value at any time, but it is effective
  371. /// only before the first call to Write(), which is when the buffers are
  372. /// allocated.
  373. /// </para>
  374. /// </remarks>
  375. public int MaxWorkers
  376. {
  377. get
  378. {
  379. return _maxWorkers;
  380. }
  381. set
  382. {
  383. if (value < 4)
  384. throw new ArgumentException("MaxWorkers",
  385. "Value must be 4 or greater.");
  386. _maxWorkers = value;
  387. }
  388. }
  389. protected override void Dispose(bool disposing)
  390. {
  391. if (this.pendingException != null)
  392. {
  393. this.handlingException = true;
  394. var pe = this.pendingException;
  395. this.pendingException = null;
  396. throw pe;
  397. }
  398. if (this.handlingException)
  399. return;
  400. if (disposing)
  401. {
  402. Stream output = this.output;
  403. if (output != null)
  404. {
  405. try
  406. {
  407. FlushOutput(true);
  408. if (!leaveOpen)
  409. output.Dispose();
  410. }
  411. finally
  412. {
  413. this.output = null;
  414. this.bw = null;
  415. }
  416. }
  417. }
  418. base.Dispose(disposing);
  419. }
  420. private void FlushOutput(bool lastInput)
  421. {
  422. if (this.emitting) return;
  423. // compress and write whatever is ready
  424. if (this.currentlyFilling >= 0)
  425. {
  426. WorkItem workitem = this.pool[this.currentlyFilling];
  427. CompressOne(workitem);
  428. this.currentlyFilling = -1; // get a new buffer next Write()
  429. }
  430. if (lastInput)
  431. {
  432. EmitPendingBuffers(true, false);
  433. EmitTrailer();
  434. }
  435. else
  436. {
  437. EmitPendingBuffers(false, false);
  438. }
  439. }
  440. /// <summary>
  441. /// Flush the stream.
  442. /// </summary>
  443. public override void Flush()
  444. {
  445. if (this.output != null)
  446. {
  447. FlushOutput(false);
  448. this.bw.Flush();
  449. this.output.Flush();
  450. }
  451. }
  452. private void EmitHeader()
  453. {
  454. var magic = new byte[] {
  455. (byte) 'B',
  456. (byte) 'Z',
  457. (byte) 'h',
  458. (byte) ('0' + this.blockSize100k)
  459. };
  460. // not necessary to shred the initial magic bytes
  461. this.output.Write(magic, 0, magic.Length);
  462. }
  463. private void EmitTrailer()
  464. {
  465. // A magic 48-bit number, 0x177245385090, to indicate the end
  466. // of the last block. (sqrt(pi), if you want to know)
  467. TraceOutput(TraceBits.Write, "total written out: {0} (0x{0:X})",
  468. this.bw.TotalBytesWrittenOut);
  469. // must shred
  470. this.bw.WriteByte(0x17);
  471. this.bw.WriteByte(0x72);
  472. this.bw.WriteByte(0x45);
  473. this.bw.WriteByte(0x38);
  474. this.bw.WriteByte(0x50);
  475. this.bw.WriteByte(0x90);
  476. this.bw.WriteInt(this.combinedCRC);
  477. this.bw.FinishAndPad();
  478. TraceOutput(TraceBits.Write, "final total : {0} (0x{0:X})",
  479. this.bw.TotalBytesWrittenOut);
  480. }
  481. /// <summary>
  482. /// The blocksize parameter specified at construction time.
  483. /// </summary>
  484. public int BlockSize
  485. {
  486. get { return this.blockSize100k; }
  487. }
  488. /// <summary>
  489. /// Write data to the stream.
  490. /// </summary>
  491. /// <remarks>
  492. ///
  493. /// <para>
  494. /// Use the <c>ParallelBZip2OutputStream</c> to compress data while
  495. /// writing: create a <c>ParallelBZip2OutputStream</c> with a writable
  496. /// output stream. Then call <c>Write()</c> on that
  497. /// <c>ParallelBZip2OutputStream</c>, providing uncompressed data as
  498. /// input. The data sent to the output stream will be the compressed
  499. /// form of the input data.
  500. /// </para>
  501. ///
  502. /// <para>
  503. /// A <c>ParallelBZip2OutputStream</c> can be used only for
  504. /// <c>Write()</c> not for <c>Read()</c>.
  505. /// </para>
  506. ///
  507. /// </remarks>
  508. ///
  509. /// <param name="buffer">The buffer holding data to write to the stream.</param>
  510. /// <param name="offset">the offset within that data array to find the first byte to write.</param>
  511. /// <param name="count">the number of bytes to write.</param>
  512. public override void Write(byte[] buffer, int offset, int count)
  513. {
  514. bool mustWait = false;
  515. // This method does this:
  516. // 0. handles any pending exceptions
  517. // 1. write any buffers that are ready to be written
  518. // 2. fills a compressor buffer; when full, flip state to 'Filled',
  519. // 3. if more data to be written, goto step 1
  520. if (this.output == null)
  521. throw new IOException("the stream is not open");
  522. // dispense any exceptions that occurred on the BG threads
  523. if (this.pendingException != null)
  524. {
  525. this.handlingException = true;
  526. var pe = this.pendingException;
  527. this.pendingException = null;
  528. throw pe;
  529. }
  530. if (offset < 0)
  531. throw new IndexOutOfRangeException(String.Format("offset ({0}) must be > 0", offset));
  532. if (count < 0)
  533. throw new IndexOutOfRangeException(String.Format("count ({0}) must be > 0", count));
  534. if (offset + count > buffer.Length)
  535. throw new IndexOutOfRangeException(String.Format("offset({0}) count({1}) bLength({2})",
  536. offset, count, buffer.Length));
  537. if (count == 0) return; // nothing to do
  538. if (!this.firstWriteDone)
  539. {
  540. // Want to do this on first Write, first session, and not in the
  541. // constructor. Must allow the MaxWorkers to change after
  542. // construction, but before first Write().
  543. InitializePoolOfWorkItems();
  544. this.firstWriteDone = true;
  545. }
  546. int bytesWritten = 0;
  547. int bytesRemaining = count;
  548. do
  549. {
  550. // may need to make buffers available
  551. EmitPendingBuffers(false, mustWait);
  552. mustWait = false;
  553. // get a compressor to fill
  554. int ix = -1;
  555. if (this.currentlyFilling >= 0)
  556. {
  557. ix = this.currentlyFilling;
  558. }
  559. else
  560. {
  561. if (this.toFill.Count == 0)
  562. {
  563. // No compressors available to fill, so... need to emit
  564. // compressed buffers.
  565. mustWait = true;
  566. continue;
  567. }
  568. ix = this.toFill.Dequeue();
  569. ++this.lastFilled;
  570. }
  571. WorkItem workitem = this.pool[ix];
  572. workitem.ordinal = this.lastFilled;
  573. int n = workitem.Compressor.Fill(buffer, offset, bytesRemaining);
  574. if (n != bytesRemaining)
  575. {
  576. if (!ThreadPool.QueueUserWorkItem( CompressOne, workitem ))
  577. throw new Exception("Cannot enqueue workitem");
  578. this.currentlyFilling = -1; // will get a new buffer next time
  579. offset += n;
  580. }
  581. else
  582. this.currentlyFilling = ix;
  583. bytesRemaining -= n;
  584. bytesWritten += n;
  585. }
  586. while (bytesRemaining > 0);
  587. totalBytesWrittenIn += bytesWritten;
  588. return;
  589. }
  590. private void EmitPendingBuffers(bool doAll, bool mustWait)
  591. {
  592. // When combining parallel compression with a ZipSegmentedStream, it's
  593. // possible for the ZSS to throw from within this method. In that
  594. // case, Close/Dispose will be called on this stream, if this stream
  595. // is employed within a using or try/finally pair as required. But
  596. // this stream is unaware of the pending exception, so the Close()
  597. // method invokes this method AGAIN. This can lead to a deadlock.
  598. // Therefore, failfast if re-entering.
  599. if (emitting) return;
  600. emitting = true;
  601. if (doAll || mustWait)
  602. this.newlyCompressedBlob.WaitOne();
  603. do
  604. {
  605. int firstSkip = -1;
  606. int millisecondsToWait = doAll ? 200 : (mustWait ? -1 : 0);
  607. int nextToWrite = -1;
  608. do
  609. {
  610. if (Monitor.TryEnter(this.toWrite, millisecondsToWait))
  611. {
  612. nextToWrite = -1;
  613. try
  614. {
  615. if (this.toWrite.Count > 0)
  616. nextToWrite = this.toWrite.Dequeue();
  617. }
  618. finally
  619. {
  620. Monitor.Exit(this.toWrite);
  621. }
  622. if (nextToWrite >= 0)
  623. {
  624. WorkItem workitem = this.pool[nextToWrite];
  625. if (workitem.ordinal != this.lastWritten + 1)
  626. {
  627. // out of order. requeue and try again.
  628. lock(this.toWrite)
  629. {
  630. this.toWrite.Enqueue(nextToWrite);
  631. }
  632. if (firstSkip == nextToWrite)
  633. {
  634. // We went around the list once.
  635. // None of the items in the list is the one we want.
  636. // Now wait for a compressor to signal again.
  637. this.newlyCompressedBlob.WaitOne();
  638. firstSkip = -1;
  639. }
  640. else if (firstSkip == -1)
  641. firstSkip = nextToWrite;
  642. continue;
  643. }
  644. firstSkip = -1;
  645. TraceOutput(TraceBits.Write,
  646. "Writing block {0}", workitem.ordinal);
  647. // write the data to the output
  648. var bw2 = workitem.bw;
  649. bw2.Flush(); // not bw2.FinishAndPad()!
  650. var ms = workitem.ms;
  651. ms.Seek(0,SeekOrigin.Begin);
  652. // cannot dump bytes!!
  653. // ms.WriteTo(this.output);
  654. //
  655. // must do byte shredding:
  656. int n;
  657. int y = -1;
  658. long totOut = 0;
  659. var buffer = new byte[1024];
  660. while ((n = ms.Read(buffer,0,buffer.Length)) > 0)
  661. {
  662. #if Trace
  663. if (y == -1) // diagnostics only
  664. {
  665. var sb1 = new System.Text.StringBuilder();
  666. sb1.Append("first 16 whole bytes in block: ");
  667. for (int z=0; z < 16; z++)
  668. sb1.Append(String.Format(" {0:X2}", buffer[z]));
  669. TraceOutput(TraceBits.Write, sb1.ToString());
  670. }
  671. #endif
  672. y = n;
  673. for (int k=0; k < n; k++)
  674. {
  675. this.bw.WriteByte(buffer[k]);
  676. }
  677. totOut += n;
  678. }
  679. #if Trace
  680. TraceOutput(TraceBits.Write,"out block length (bytes): {0} (0x{0:X})", totOut);
  681. var sb = new System.Text.StringBuilder();
  682. sb.Append("final 16 whole bytes in block: ");
  683. for (int z=0; z < 16; z++)
  684. sb.Append(String.Format(" {0:X2}", buffer[y-1-12+z]));
  685. TraceOutput(TraceBits.Write, sb.ToString());
  686. #endif
  687. // and now any remaining bits
  688. TraceOutput(TraceBits.Write,
  689. " remaining bits: {0} 0x{1:X}",
  690. bw2.NumRemainingBits,
  691. bw2.RemainingBits);
  692. if (bw2.NumRemainingBits > 0)
  693. {
  694. this.bw.WriteBits(bw2.NumRemainingBits, bw2.RemainingBits);
  695. }
  696. TraceOutput(TraceBits.Crc," combined CRC (before): {0:X8}",
  697. this.combinedCRC);
  698. this.combinedCRC = (this.combinedCRC << 1) | (this.combinedCRC >> 31);
  699. this.combinedCRC ^= (uint) workitem.Compressor.Crc32;
  700. TraceOutput(TraceBits.Crc,
  701. " block CRC : {0:X8}",
  702. workitem.Compressor.Crc32);
  703. TraceOutput(TraceBits.Crc,
  704. " combined CRC (after) : {0:X8}",
  705. this.combinedCRC);
  706. TraceOutput(TraceBits.Write,
  707. "total written out: {0} (0x{0:X})",
  708. this.bw.TotalBytesWrittenOut);
  709. TraceOutput(TraceBits.Write | TraceBits.Crc, "");
  710. this.totalBytesWrittenOut += totOut;
  711. bw2.Reset();
  712. this.lastWritten = workitem.ordinal;
  713. workitem.ordinal = -1;
  714. this.toFill.Enqueue(workitem.index);
  715. // don't wait next time through
  716. if (millisecondsToWait == -1) millisecondsToWait = 0;
  717. }
  718. }
  719. else
  720. nextToWrite = -1;
  721. } while (nextToWrite >= 0);
  722. } while (doAll && (this.lastWritten != this.latestCompressed));
  723. if (doAll)
  724. {
  725. TraceOutput(TraceBits.Crc,
  726. " combined CRC (final) : {0:X8}", this.combinedCRC);
  727. }
  728. emitting = false;
  729. }
  730. private void CompressOne(Object wi)
  731. {
  732. // compress and one buffer
  733. WorkItem workitem = (WorkItem) wi;
  734. try
  735. {
  736. // compress and write to the compressor's MemoryStream
  737. workitem.Compressor.CompressAndWrite();
  738. lock(this.latestLock)
  739. {
  740. if (workitem.ordinal > this.latestCompressed)
  741. this.latestCompressed = workitem.ordinal;
  742. }
  743. lock (this.toWrite)
  744. {
  745. this.toWrite.Enqueue(workitem.index);
  746. }
  747. this.newlyCompressedBlob.Set();
  748. }
  749. catch (System.Exception exc1)
  750. {
  751. lock(this.eLock)
  752. {
  753. // expose the exception to the main thread
  754. if (this.pendingException!=null)
  755. this.pendingException = exc1;
  756. }
  757. }
  758. }
  759. /// <summary>
  760. /// Indicates whether the stream can be read.
  761. /// </summary>
  762. /// <remarks>
  763. /// The return value is always false.
  764. /// </remarks>
  765. public override bool CanRead
  766. {
  767. get { return false; }
  768. }
  769. /// <summary>
  770. /// Indicates whether the stream supports Seek operations.
  771. /// </summary>
  772. /// <remarks>
  773. /// Always returns false.
  774. /// </remarks>
  775. public override bool CanSeek
  776. {
  777. get { return false; }
  778. }
  779. /// <summary>
  780. /// Indicates whether the stream can be written.
  781. /// </summary>
  782. /// <remarks>
  783. /// The return value depends on whether the captive stream supports writing.
  784. /// </remarks>
  785. public override bool CanWrite
  786. {
  787. get
  788. {
  789. if (this.output == null) throw new ObjectDisposedException("BZip2Stream");
  790. return this.output.CanWrite;
  791. }
  792. }
  793. /// <summary>
  794. /// Reading this property always throws a <see cref="NotImplementedException"/>.
  795. /// </summary>
  796. public override long Length
  797. {
  798. get { throw new NotImplementedException(); }
  799. }
  800. /// <summary>
  801. /// The position of the stream pointer.
  802. /// </summary>
  803. ///
  804. /// <remarks>
  805. /// Setting this property always throws a <see
  806. /// cref="NotImplementedException"/>. Reading will return the
  807. /// total number of uncompressed bytes written through.
  808. /// </remarks>
  809. public override long Position
  810. {
  811. get
  812. {
  813. return this.totalBytesWrittenIn;
  814. }
  815. set { throw new NotImplementedException(); }
  816. }
  817. /// <summary>
  818. /// The total number of bytes written out by the stream.
  819. /// </summary>
  820. /// <remarks>
  821. /// This value is meaningful only after a call to Close().
  822. /// </remarks>
  823. public Int64 BytesWrittenOut { get { return totalBytesWrittenOut; } }
  824. /// <summary>
  825. /// Calling this method always throws a <see cref="NotImplementedException"/>.
  826. /// </summary>
  827. /// <param name="offset">this is irrelevant, since it will always throw!</param>
  828. /// <param name="origin">this is irrelevant, since it will always throw!</param>
  829. /// <returns>irrelevant!</returns>
  830. public override long Seek(long offset, System.IO.SeekOrigin origin)
  831. {
  832. throw new NotImplementedException();
  833. }
  834. /// <summary>
  835. /// Calling this method always throws a <see cref="NotImplementedException"/>.
  836. /// </summary>
  837. /// <param name="value">this is irrelevant, since it will always throw!</param>
  838. public override void SetLength(long value)
  839. {
  840. throw new NotImplementedException();
  841. }
  842. /// <summary>
  843. /// Calling this method always throws a <see cref="NotImplementedException"/>.
  844. /// </summary>
  845. /// <param name='buffer'>this parameter is never used</param>
  846. /// <param name='offset'>this parameter is never used</param>
  847. /// <param name='count'>this parameter is never used</param>
  848. /// <returns>never returns anything; always throws</returns>
  849. public override int Read(byte[] buffer, int offset, int count)
  850. {
  851. throw new NotImplementedException();
  852. }
  853. // used only when Trace is defined
  854. [Flags]
  855. enum TraceBits : uint
  856. {
  857. None = 0,
  858. Crc = 1,
  859. Write = 2,
  860. All = 0xffffffff,
  861. }
  862. [System.Diagnostics.ConditionalAttribute("Trace")]
  863. private void TraceOutput(TraceBits bits, string format, params object[] varParams)
  864. {
  865. if ((bits & this.desiredTrace) != 0)
  866. {
  867. lock(outputLock)
  868. {
  869. int tid = Thread.CurrentThread.GetHashCode();
  870. #if FEATURE_FULL_CONSOLE
  871. Console.ForegroundColor = (ConsoleColor) (tid % 8 + 10);
  872. #endif
  873. Console.Write("{0:000} PBOS ", tid);
  874. Console.WriteLine(format, varParams);
  875. #if FEATURE_FULL_CONSOLE
  876. Console.ResetColor();
  877. #endif
  878. }
  879. }
  880. }
  881. }
  882. }