iTextSharp Multithread on PdfReader goes into dead end

时间:2017-12-18 06:57:11

标签: c# multithreading

In our production server, we create thousands of pdf documents by wkhtmltopdf and sign them via iTextSharp using HSM. Most of the operation performed at night. But after we performed an update for multithreading we have been stack into a serious situation. After working a long time, our WCF service starts to give exception for every request:

System.IndexOutOfRangeException: Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. This also applies to classes like StreamWriter and StreamReader. 
 at System.Buffer.InternalBlockCopy(Array src, Int32 srcOffsetBytes, Array dst, Int32 dstOffsetBytes, Int32 byteCount
 at System.IO.StreamWriter.Write(Char[] buffer, Int32 index, Int32 count
 at System.IO.TextWriter.SyncTextWriter.WriteLine(String value
 at iTextSharp.text.pdf.PdfReader..ctor(IRandomAccessSource byteSource, Boolean partialRead, Byte[] ownerPassword, X509Certificate certificate, ICipherParameters certificateKey, Boolean closeSourceOnConstructorError
 at iTextSharp.text.pdf.PdfReader..ctor(RandomAccessFileOrArray raf, Byte[] ownerPassword, Boolean partial
 at Turkkep.Daemons.Crypto.PDFSignerService.sign(Byte[] sourceData, Stream destination
 at Turkkep.Daemons.Crypto.PDFSignerService.Sign(Byte[] sourceData) Server stack trace: at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter
 at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc
 at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout
 at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation
 at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown at [0]: at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg
 at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type
 at Turkkep.Core.Signature.IPDFSigner.Sign(Byte[] sourceData
 at Turkkep.Shared.Signature.PDFSigner.Sign(Byte[] sourceData)
   --- End of inner exception stack trace --- at Turkkep.Shared.Signature.PDFSigner.Sign(Byte[] sourceData
 at Turkkep.Shared.Daemon.InvoiceWorker.getPdfDocument(InvoiceType12 ublInvoice, Int64 organization
 at Turkkep.Shared.Daemon.InvoiceWorker.processEArchive(Invoice invoice, String signedInvoice, InvoiceType12 ublInvoice
 at Turkkep.Shared.Daemon.InvoiceWorker.sendInvoice12(Invoice invoice)

Our signing code for WCF service is;

    private void sign(byte[] sourceData, Stream destination)
    {
        RandomAccessFileOrArray source = new RandomAccessFileOrArray(sourceData);
        using (var reader = new PdfReader(source, null))
        {
            using (var stamper = PdfStamper.CreateSignature(reader, destination, '\0'))
            {
                var appearance = stamper.SignatureAppearance;
                appearance.Reason = _reason;
                appearance.Location = _location;
                appearance.SetVisibleSignature(new Rectangle(0, 0, 0, 0), 1, String.Format("TurkKEP-{0}-{1}-{2}", DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day));
                var externalSigner = new PDFExternalSigner("SHA-256");

                ICollection<X509Certificate> chain = getChain(externalSigner.GetActiveConnection());

                MakeSignature.SignDetached(appearance, externalSigner, chain, null, null, null, _estimatedSize, CryptoStandard.CMS);
            }
        }
    }

    public byte[] Sign(byte[] sourceData)
    {
        try
        {
            using (MemoryStream destination = new MemoryStream())
            {
                sign(sourceData, destination);
                return destination.ToArray();
            }
        }
        catch (FaultException)
        {
            throw;
        }
        catch (Exception ex)
        {
            _logger.Error("PDFSignerService.Sign", ex);
            throw new Exception(ex.ToString());
        }
    }

I think locking the PdfReader can solve the problem but I don't want to weak multithread power. Getting an exception for some messages is not a problem. In this situation service never works again until we restart it.

Note: Production server is up to date. We've just updated.

Edit: Last night we have caught similar exception with different stacktrace:

    19.12.2017 04:03:08 
System.Exception: PDF sign failed. Crypto service communication exception occured.
     ---> System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: System.IndexOutOfRangeException: Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. This also applies to classes like StreamWriter and StreamReader.
     at System.Buffer.InternalBlockCopy(Array src, Int32 srcOffsetBytes, Array dst, Int32 dstOffsetBytes, Int32 byteCount
     at System.IO.StreamWriter.Write(Char[] buffer, Int32 index, Int32 count
     at System.IO.TextWriter.SyncTextWriter.WriteLine(String value
     at iTextSharp.text.pdf.PdfStamperImp.Close(PdfIndirectReference info, Int32 skipInfo
     at iTextSharp.text.pdf.PdfStamperImp.Close(IDictionary`2 moreInfo
     at iTextSharp.text.pdf.PdfSignatureAppearance.PreClose(Dictionary`2 exclusionSizes
     at iTextSharp.text.pdf.security.MakeSignature.SignDetached(PdfSignatureAppearance sap, IExternalSignature externalSignature, ICollection`1 chain, ICollection`1 crlList, IOcspClient ocspClient, ITSAClient tsaClient, Int32 estimatedSize, CryptoStandard sigtype, SignaturePolicyIdentifier signaturePolicy
     at Turkkep.Daemons.Crypto.PDFSignerService.sign(Byte[] sourceData, Stream destination
     at Turkkep.Daemons.Crypto.PDFSignerService.Sign(Byte[] sourceData) Server stack trace: at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter
     at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc

After this exception service throws same exception for every call.

Now we are going to change code as following for moving the PdfStamper out of the using block.

private void sign(byte[] sourceData, Stream destination)
{
    RandomAccessFileOrArray source = new RandomAccessFileOrArray(sourceData);
    using (var reader = new PdfReader(source, null))
    {
        try
        {
            var stamper = PdfStamper.CreateSignature(reader, destination, '\0');
            var appearance = stamper.SignatureAppearance;
            appearance.Reason = _reason;
            appearance.Location = _location;
            appearance.SetVisibleSignature(new Rectangle(0, 0, 0, 0), 1, String.Format("TurkKEP-{0}-{1}-{2}", DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day));
            var externalSigner = new PDFExternalSigner("SHA-256");

            ICollection<X509Certificate> chain = getChain(externalSigner.GetActiveConnection());

            MakeSignature.SignDetached(appearance, externalSigner, chain, null, null, null, _estimatedSize, CryptoStandard.CMS);
            stamper.Dispose();
        }
        catch (Exception ex)
        {
            _logger.Error("Document sign failed unexpectedly.", ex);
            throw new FaultException("Document sign failed unexpectedly. Exception: " + ex.ToString());
        }
    }
}

0 个答案:

没有答案