Open Bug 1121842 Opened 9 years ago Updated 5 months ago

[META] RFC: C-C Thunderbird - Cleaning of incorrect Close, unchecked Flush, Write etc. in nsPop3Sink.cpp and friends.

Categories

(MailNews Core :: Networking: POP, defect)

defect
Not set
major

Tracking

(Not tracked)

People

(Reporter: ishikawa, Assigned: ishikawa)

References

(Depends on 1 open bug, Blocks 1 open bug)

Details

(Keywords: meta)

This is to be a meta bug entry for the following issues.

I depend on TB for the workflow at the office and my personal correspondence
on several PCs (under linux and Windows).
So its correct operation (and smooth operation hopefully) as a cross-platform mailer is very important for me. So I files this bugzilla entry.

Problem:

There is a confusion as to which routine should be responsible for
closing the file stream associated with a variable m_outFileStream in
nsPop3Sink.cpp.

Because of the confusion (?), there are multiple instances of
unnecessary extra bogus Close calls across a few files.

Also, Pop3 code is full of unchecked |Close|, |Flush|, and |Write| calls.

(Imap code, too. That will be needed to be taken care of
eventually. Sorry I am not using imap right now and not feel motivated
to tackle that group of files immediately until nsPops3Sink.cpp is
fixed. But someone can take the lead after seeing this.)

If the underlying file system experiences a glitch, then it is quite
likely that thunderbird treats failed download as success and delete
the message on the POP3 server happily. Totally unacceptable behavior.

[ The glitch of the file system: almost filled-up file system,
transient network problems for remotely mounted file system, incorrect
file system permission caused by administrative error an an NFS server,
USB memory stick where file is to be stored fell of the PC (!) etc.
I have personally experienced and confirmed the first three error
scenarios with TB before in different context and experienced data loss.
I know some people seemed to have suffered from the last one
according to bugzilla entries. ]

Background of discovery:

I tried to enable output buffering in nsPop3Sink.cpp for performance
reasons.
[  Bug 1116055 - Performance issue: Failure to use buffered write (comm-central thunderbird) ]

But When I enabled output buffering in nsPop3Sink.cpp in addition to
the patch in bug 1116055, it caused the failure to incorporate download
messages. It looks timing dependent.  Anyway, I always found a stream closed
prematurely before writing finishes.

That prompted me to debug and study the code carefully.  Then I found
that there are bogus Close() calls to already closed file streams in
the code.

This bogus |Close()| became apparent as soon as I added error checking
of returned value of |Close()| in several places.

Such extra bogus |Close| calls are RAMPANT  during execution, and I
confirm it after static code analysis.

But then, when I started to think of how to fix the situation,
I noticed there seemed to be a confusion about which function
should call |Close()| on the buffer stream variable, m_outFileStream
in nsPop3Sink.cpp.

So I traced the history of m_outFileStream (where it is set, where it
is used as parameter to external functions [which in turn may Close
it], when the file associated with it are Opened, Closed, etc.)

After the analysis, I came up with a plan to clean up the current
incorrect code that invokes bogus |Close| on already closed
streams. (Such bogus calls interfere with smooth gdb debugging. There
are simply too many such calls that would return
NS_BASE_STREAM_CLOSED during execution. Oh yes, come to think of it
buffered output routine does not report error situation completely to my taste.
(See Bug 1120046 - RFC mozilla/netwerk/base/src/nsBufferedStreams.cpp: better error reporting and maybe adding thread-race lock )
And the source code is not quite right statically, too, even if calling Close on already closed stream should be NOOP in principle.
It is too confusing to see an already closed
stream closed only several lines down while reading the code. )

We should also check the return value of Close, Flush and Write
properly.

========================================
Plan for Improvement
========================================

My Plan to clean up the code is as follows.

There are four steps.

Step 1. Remove Extra ->Close() calls (and Flush() before Close().).

Let us remove extra / unnecessary / bogus |Close| calls.

The removal is based on the two proposals below.

PROPOSAL-1: DiscardNewMessage should not close the file stream passed
as the 1st argument. It is caller's responsibility to close it..

PROPOSAL-2: FinishNewMessage should not close the
file stream passed as the first argument.
It is caller's responsibility to close it.

The reasoning is given in a crude write-up I created from checking the
code at the end. (This will be posted as the next comment.)

I checked the usage of two functions above, and with a few patches,
the proposals ought to work.

This patch will be filed as a different bugzilla on which this meta
bugzilla entry depends.

After the removal of unnecessary calls of |Close|,
we go to Step-2.

Step-2. Add error value checking of Close() and Flush();

   First step. Simply add NS_ERROR(). Better than nothing.

   At least, we will see the error printed during testing of
   DEBUG version of TB.
  (For better error handling, we will wait for Step 4.)
   
The patch for this will be posted as a separate bugzilla entry.

After Close() and Flush() are taken care of, we check
the error return of Write() and the mismatch of
the # of written bytes and requested bytes.
   
Step-3. Add error value checking of Write();
   Check the return value of Write and
   if the requested # of bytes matches the # of really written bytes.
   
   First step. Simply add NS_ERROR(). Better than nothing.
   At least, we will see the error printed during testing of
   DEBUG version of TB.
  (For better error handling, we will wait for Step 4.)

The patch for this will be posted as a separate bugzilla entry.

Step-4. More error processing
   I understand that some places may need elaborate error handling,
   better than NS_ERROR(),  and
   error return path.  Take care of them in Step-4.

   The patch for this will be posted as a separate bugzilla entry.  I
   think I will need a few different bugzilla entries since in a few
   places, the error handling seems to be necessarily complex.

Step-5. Enabling buffered output.

  (This is not a correctness fix. This is a performance improvement.)

  Additionally, introduce buffering output to the file stream
  created in these files appropriately.

I will post the crude memo/write-up in the next comment.
Here is a write-up.

Below is the writeup I created initially as comments to nsPop3Sink.cpp
while I checked the usage history of m_outFileStream.
It was created initially as comments on pristine nsPop3Sink.cpp, and
"hg diff" was used to extract it, and then I manually edied it into a
text form.

So sorry for irregular formating. But essence of
what I found to come up with the two proposals is in the following.

//
// m_outFileStream starts as nullptr in new nsPop3Sink.
//
// ========================================
// External Usage of m_outFileStream
// ----------------------------------------
// Where is it passed to routines ouside nsPop3Sink.cpp?

// (we need to figure out if there are Close operation using the
//  value to track down strange premature Closing of underlying file stream
//  when buffering is enabled.):
/

m_outFileStream is passed to external routine in a few places.
Let us take a look at them in turn.

// - IncorporateBegin
//
//  rv = m_newMailParser->Init(serverFolder, m_folder,
//                             m_window, newHdr, m_outFileStream);
//
//       Init() copies m_outFileStream to
//       m_newMailParser->m_outFileStream
// 
//       So the question becomes WHERE/WHEN m_outFilestream is Closed in
//       mailnews/local/src/nsParseMailbox.cpp
//         
//       Short Answer: m_outFilestream->Close() will be done inside
//         the two calles in DiscardNewMessage().
//

	[ After the analysis below I came up with the FINAL PROPOSAL: 
	DiscardNewMessage should not close the file stream passed  as the
	1st argument. It is caller's responsibility. ]

//       Long Answer: 
//           - mailnews/local/src/nsParseMailbox.cpp has
              two calls to DiscardNewMessages.

//           (a)  nsCOMPtr<nsIMsgPluggableStore> msgStore;
//              nsresult rv =
//              m_downloadFolder->GetMsgStore(getter_AddRefs(msgStore));
//              rv = msgStore->DiscardNewMessage(m_outputStream, m_newMsgHdr);
//
//           (b)  nsCOMPtr<nsIMsgPluggableStore> store;
//             rv = m_downloadFolder->GetMsgStore(getter_AddRefs(store));
//             store->DiscardNewMessage(m_outputStream, mailHdr);
//

	There are two implemenations of DiscardNewMessages.

//             mailnews/local/src/nsMsgBrkMBoxStore.cpp 
//             as member of class nsMsgBrkMBoxStore -- nsMsgBrkMBoxStore::DiscardNewMessage(nsIOutputStream *aOutputStream, 
//               m_outputStream-> Close is called since
//               aOutputStream->Close() is called.
//
//             mailnews/local/src/nsMsgMaildirStore.cpp 
//             as member of class nsMsgMaildirStore -- nsMsgMaildirStore::DiscardNewMessage(nsIOutputStream *aOutputStream, 
//               Same as in nsMsgBrkMBoxStore.cpp
//               m_outputStream-> Close is called since
//               aOutputStream->Close() is called.
//

	So DiscardNewMessage calls Close always now.

	[ In the end, I am proposing that the aOutputStream->Close()
	is removed from DiscardNewMessage(), and it is caller's
	responsibility to close the stream.]

m_outFileStream is passed to external routine in:

// - IncorporateComplete

//  case - (a)     if (m_downloadingToTempFile) path
//
//      This has the following code snipet.
//      *AFTER* |Close|, it is referenced.
//
//      m_outFileStream->Close();
//      ...
//      nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream);
//      rv = MsgReopenFileStream(m_tmpDownloadFile, inboxInputStream);
//       .... 
//
//      DONE: here is what I found out what happens in MsgReopenFileStream.
//
//        mailnews/base/util/nsMsgUtils.cpp 
//           nsresult MsgReopenFileStream(nsIFile *file, nsIInputStream *fileStream) 
//             InitWithFile is called()
//             nsMsgFileStream *msgFileStream = static_cast<nsMsgFileStream *>(fileStream);
//             if (msgFileStream)
//                  return msgFileStream->InitWithFile(file);
//
//             *THEN* what about InitWithFile.
//             
//             Short ANSER: Opened for R/w (and mFileSesc is updated.)
//             XXX: TODO/FIXME? Shouldnt we re-buffer this stream.

	       There is a performance issue. We may want to perform
	       buffered output.
	       
//
//             mailnews/base/util/nsMsgFileStream.cpp
//             member of class nsMsgFileStream -- nsresult nsMsgFileStream::InitWithFile(nsIFile *file) 
//                RW file is opened.
//                file->OpenNSPRFileDesc(PR_RDWR|PR_CREATE_FILE, 0664, &mFileDesc
// 

              The following three |InitWithFile()|s seem to be unrelated to
	      File stream.
	      
//             mozilla/netwerk/ipc/RemoteOpenFileChild.cpp (View Hg log or Hg annotations)
//             member of class RemoteOpenFileChild -- RemoteOpenFileChild::InitWithFile(nsIFile *aFile)   
//                 NOT_IMPLEMENTED
//             mozilla/xpcom/io/nsLocalFileWin.cpp 
//             member of class nsLocalFile -- nsLocalFile::InitWithFile(nsIFile* aFile) 
//                 This one seems to set pathname to a field only. no opening???
//             mozilla/xpcom/io/nsLocalFileCommon.cpp (View Hg log or Hg annotations)
//             member of class nsLocalFile -- nsLocalFile::InitWithFile(nsIFile* aFile) 
//                 This one seems to set pathname to a field only. no opening???
//                 (after character code conversion possibly.)


an nsIInputStream related to m_outFileStream:

//  
//      NOTE: There is a filestream related to m_outFileStream.
//      nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream);
// This is created and used inside 
// nsPop3Sink::IncorporateComplete(nsIMsgWindow *aMsgWindow, int32_t aSize)
//  
//      The name is inboxInputStream, and it is passed to 
//      |AppendMsgFromStream|.
//
//        rv = m_newMailParser->AppendMsgFromStream(inboxInputStream, hdr,
//                                                    msgSize, m_folder);
//
//      I wonder what happens in AppendMsgFromStream to inboxInputStream
//      and what happens to m_outFileStream as a result.

//
//      DONE: investigated.
//

         tidied up AppendMsgFromStream listing from mxr

//      mailnews/local/src/nsParseMailbox.h (View Hg log or Hg annotations)
           nsresult AppendMsgFromStream(nsIInputStream *fileStream, nsIMsgDBHdr *aHdr, 

Referenced (in 2 files total) in:

          mailnews/local/src/nsParseMailbox.cpp (View Hg log or Hg annotations)
 	    line 2367 -- nsresult nsParseNewMailState::AppendMsgFromStream(nsIInputStream *fileStream, 

	     This is the definition of AppendMsgFromStream.


//           fileStream is used for *READING*
//           No explicit close of it (?).
//           output for append is done by creating a filestream internally to
//           AppendMsgFromStream and is closed there (or in
//           store->FinishNewMessage.)

	      PERFORMANCE ISSUE
	      TODO/FIXME: make this outupt stream perform buffered output.


//	 mailnews/local/src/nsParseMailbox.cpp (View Hg log or Hg annotations)
	
         line 2507 -- rv = AppendMsgFromStream(inputStream, newHdr, messageLength, destIFolder); 
        Caller does not seem to close inputStream explicitly (directly).

        mailnews/local/src/nsPop3Sink.cpp (View Hg log or Hg annotations)
        line 815 -- rv = m_newMailParser->AppendMsgFromStream(inboxInputStream, hdr, 
	This is the reference within nsPop3Sink.cpp


m_outFileStream is passed to external routine (cont'ed):

2nd path in IncpoprateComplete
// - IncorporateComplete

//  case- (b) if (m_downloadingToTempFile) ... ELSE  path

//      m_msgStore->FinishNewMessage(m_outFileStream, hdr);
//      Will it be |Closed| in FinishNewMessage?

    Below is the problem noticed immediately.

    INCONSISTENT HANDLING of the 1st file stream argument inside
    FinishNewMessage defined in nsMsgBrkMBoxStore.cpp and
    nsMsgMaildirStore.cpp (the latter closes it) while the former does not
    touch it.

// ../../../mailnews/local/src/nsMsgBrkMBoxStore.cpp:711:nsMsgBrkMBoxStore::FinishNewMessage(nsIOutputStream *aOutputStream,
//
//      NOOP: nothing happens to m_outFileStream.

// ../../..//mailnews/local/src/nsMsgMaildirStore.cpp:684:nsMsgMaildirStore::FinishNewMessage(nsIOutputStream *aOutputStream,
//
//       Closed!
//       m_outFileStream->Close() called.
//             (since aOuptutStream->Close() is called. )
//

	[ After the analysis like this I came up with the FINAL
	PROPOSAL: FinishNewMessage should not close the file stream
	passed as the first argument.
	It is caller's responsibility to close it.]

In order to verify that this proposal works, I checked what the
callers of FinishNewMessage expect and do today?  (See the analysis at
the end.) In a nustshell, though, most of the callers assume that the
caller needs to close the stream.

m_outFileStream is passed to externa routine in: (con'ted)

// - IncorporateAbort
//
//   *AFTER* |Close|, it is passed to DiscardNewMessage
//
//     nsresult rv = m_outFileStream->Close();
//      less some error checking
//      m_msgStore->DiscardNewMessage(m_outFileStream,
//                                    m_newMailParser->m_newMsgHdr);
//
//    What happens to m_outFileStream in DiscardNewMessage?
//
//    DONE: Here is what I found out.
//    XXX TODO/FIXME : 
//    BAD: although it is already Closed,  DiscardNewMessage again
//    Close() it inside.
//

========================================
Where is a file stream opened related to m_outFileStream?
========================================

// 
// |Open|
//
// Where does m_outFileStream is created [opened]?
// - IncorporateBegin:

//   Created either from a temp file:
//   rv = MsgGetFileStream(m_tmpDownloadFile, getter_AddRefs(m_outFileStream));

//   or from  (less error checking)
//    rv = server->GetMsgStore(getter_AddRefs(m_msgStore));
//    m_msgStore->GetNewMsgOutputStream(m_folder, getter_AddRefs(newHdr),
//                                    &reusable, getter_AddRefs(m_outFileStream));

========================================
Where is a file stream related to m_outFileStream Closed?
========================================

I found out numerous bogus calls to already closed stream.
This is bad.

// 
// |Close| in nsPop3Sink.cpp
//
// Where does m_outFileStream is closed?
// - EndMailDelivery: closes it and set m_outFileStream to nullptr.
// - AbortMailDelivery: ditto.
// - IncorporateComplete: closes it.
      Maybe we should set m_outFileStream to nullptr?

// - IncorporateAbort
//   But after the close, m_outFileStream is passed to
//      m_msgStore->DiscardNewMessage(m_outFileStream,
//                                    m_newMailParser->m_newMsgHdr);
//      which again calls ->Close inside.
	 This looks wrong/BAD (XXX): Double Close.

//   
//
// When a seekable file stream created from
// an NSIFile is closed, what happens to the original file stream?
// (its underlying low-level file stream, that is )
// 
//  XXX TODO: find out
//
// seekableOutStream is created:
// - WriteLineToMailbox
//   nsCOMPtr <nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream);
//      seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
//      to write to the end of the file.
//
// The following was investigated alreay elsewhere
// What happens to m_outFileStream in
// -    nsPop3Sink::IncorporateComplete
//      m_outFileStream->Close();
//      m_newMailParser->FinishHeader();
//      ...
//      nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream);
//      rv = MsgReopenFileStream(m_tmpDownloadFile, inboxInputStream);
//     


// a few extra fixes.

  // ??? XXX: Following is no longer used? Strange??? TODO/FIXME: check out why
   nsCOMPtr<nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream);

Fix multiple close in nsPop3Sink::IncorporateComplete(nsIMsgWindow *aMsgWindow, int32_t aSize)

Removes extra Flush()

========================================


========================================
What do callers of FinishNewMessage expect?
----------------------------------------

|FinishNewMessage|

 tidied up listing of  FinishNewMessage 

    mailnews/base/public/nsIMsgPluggableStore.idl (View Hg log or Hg annotations)
        line 157 -- void finishNewMessage(in nsIOutputStream aOutputStream, 

149   /**
150    * Must be called by code that calls getNewMsgOutputStream to finish
151    * the process of storing a new message, if the new msg has not been
152    * discarded. Could/should this be combined with discardNewMessage?
153    *
154    * @param aOutputStream stream we were writing the message to.
155    * @param aNewHdr header of message finished.
156    */
157   void finishNewMessage(in nsIOutputStream aOutputStream,
158                          in nsIMsgDBHdr aNewHdr);
159 

    The spec above is not clear about what it does on the file stream
    argument.

    mailnews/base/util/nsMsgDBFolder.cpp (View Hg log or Hg annotations)
        line 1788 -- msgStore->FinishNewMessage(m_tempMessageStream, m_offlineHeader); 

	OK: Close the stream on the caller side.
	    the code below clearly shows that the caller side closes it.

	XXX/BAD: no check on the return value of Close();

	1787   if (msgStore)
	1788     msgStore->FinishNewMessage(m_tempMessageStream, m_offlineHeader);
	1789 
	1790   m_offlineHeader = nullptr;
	1791   if (m_tempMessageStream)
	1792   {
	1793     m_tempMessageStream->Close();
	1794     m_tempMessageStream = nullptr;
	1795   }
	1796   return NS_OK;

    mailnews/import/applemail/src/nsAppleMailImport.cpp (View Hg log or Hg annotations)
        line 564 -- msgStore->FinishNewMessage(outStream, msgHdr); 

	OK: Close the stream on the caller side.
	XXX: No check on the return value of Close();
	XXX/BAD BAD BAD: Double Close() 

      // add the data to the mbox stream
562       if (NS_SUCCEEDED(nsEmlxHelperUtils::AddEmlxMessageToStream(currentEntry, outStream))) {
563         mProgress++;
564         msgStore->FinishNewMessage(outStream, msgHdr);
565       }
566       else {
567         msgStore->DiscardNewMessage(outStream, msgHdr);

	    Since DiscardNewMessage() closes outStream, we would  call Close() extra times below.

568         break;
569       }
570       if (!reusable)
571         outStream->Close();

            set outStream to nullptr to avoid double Close() more more time
	    again below :-( 

572     }
573     if (outStream)
574       outStream->Close();

	  [ After the analysis like this, I came up with the FINAL
	    PROPOSAL: DiscardNewMessage should not close the file
	    stream passed as its first argument. It is caller's
	    responsibility to close it.]

           XXX: possibility for a fix. Do not let DiscardNewMessage
           close the stream.  We need to check the usage of
	   DiscardNewMessage to do this (TODO/FIXME).  What do
	   calles of DiscardNewMessage expect?  See the analysis
	   at the end. The analysis showed my final proposal is OK.

    mailnews/import/eudora/src/nsEudoraMailbox.cpp (View Hg log or Hg annotations)
        line 307 -- msgStore->FinishNewMessage(outputStream, msgHdr);
	OK Caller side close
	XXX: no check on Close
	XXX: multiple Closes

        line 423 -- msgStore->FinishNewMessage(outputStream, msgHdr); 
	OK Caller side close
	XXX: no check on Close
	XXX: multiple Closes

    mailnews/import/oexpress/nsOE5File.cpp (View Hg log or Hg annotations)
        line 471 -- msgStore->FinishNewMessage(outputStream, msgHdr); 
	OK Caller side close
	XXX: no check on Close
	XXX: multiple Closes

    mailnews/import/oexpress/nsOEMailbox.cpp (View Hg log or Hg annotations)
        line 330 -- m_msgStore->FinishNewMessage(m_dstOutputStream, msgHdr); 

	NEED CAUTION
	NG: No Caller side close
	it returns without closing a file stream. !?
	XXX: TODO/FIXME: probably should close.

    mailnews/import/outlook/src/nsOutlookMail.cpp (View Hg log or Hg annotations)
        line 427 -- msgStore->FinishNewMessage(outputStream, msgHdr); 
	OK Caller side close
	XXX: no check on Close
	XXX: multiple Closes

Referenced (in 8 files total) in:

    mailnews/imap/src/nsImapMailFolder.cpp (View Hg log or Hg annotations)
        line 7280 -- offlineStore->FinishNewMessage(outputStream, newMailHdr);
	OK caller side close. (outputStream is created in the caller function.)
	XXX: no check on close
	
        line 8385 -- msgStore->FinishNewMessage(offlineStore, fakeHdr); 
	OK caller side close (offlineStore is created in the caller function.)
	XXX: no check on close

    mailnews/imap/src/nsImapService.cpp (View Hg log or Hg annotations)
        line 2096 -- msgStore->FinishNewMessage(offlineStore, fakeHdr); 
	OK caller side close
	XXX: no check on close

    mailnews/local/src/nsLocalMailFolder.cpp (View Hg log or Hg annotations)
        line 2258 -- rv = mCopyState->m_msgStore->FinishNewMessage(mCopyState->m_fileStream,

	OK Caller side close.
	XXX: no check on the return value of Close

	XXX: TODO/FIXME: BUG if FinishNewMessage closes the stream
	(the 1st argument) we may have a serious problem!? from the
	code below.  (I have no idea what happens if
	!multipleCopiesFinished on line 2262.
	2258     rv = mCopyState->m_msgStore->FinishNewMessage(mCopyState->m_fileStream,
	2259                                                   mCopyState->m_newHdr);
	2260     if (NS_SUCCEEDED(rv) && mCopyState->m_newHdr)
	2261       mCopyState->m_newHdr->GetMessageKey(&mCopyState->m_curDstKey);
	2262     if (multipleCopiesFinished)
	2263       mCopyState->m_fileStream->Close();
	2264     else
	2265       mCopyState->m_fileStream->Flush();



        line 2582 -- rv = mCopyState->m_msgStore->FinishNewMessage(mCopyState->m_fileStream,
	OK Caller side Close.
	XXX: no check on the return value of Close.

        line 3595 -- msgStore->FinishNewMessage(outFileStream, newHdr); 
	OK Caller side Close
	XXX: no check on the return value of Close.
	

    mailnews/local/src/nsMovemailService.cpp (View Hg log or Hg annotations)
        line 499 -- rv = msgStore->FinishNewMessage(outputStream, newHdr);
	OK Caller Close
	XXX: no check on the return value of Close

	Possible Serious Issue.
	XXX/TODO FIXME: somewhat warped code structure.
	     I am not sure if outputStream may
	     be assigned different file streams during While loop and
	     if that is the case, then we fail to close all of them except
	     the last one (we close it outside the while loop).
	     I wonder while loop is executed only once. But I have no idea.
	     Maybe we can use the Close immediately after use inside
	     the loop. : that is,
	      	 // Finish previous header and message, if any.
		 if (newHdr) { 
		 ...
		 at the end.
		 }

            In any case, better comment would be a good thing to have here.
	

        line 544 -- rv = msgStore->FinishNewMessage(outputStream, newHdr); 

	OK Caller side Close
	XXX: no check on the return value of Close

    mailnews/local/src/nsParseMailbox.cpp (View Hg log or Hg annotations)
        line 2417 -- return store->FinishNewMessage(destOutputStream, aHdr); 
	
	NG a rare *EXCEPTION*...

	*CAUTION*:
	The caller expects FinishNewMessage to close the stream!
	XXX: no check on close.
	XXX: double Close when the stream is a reusable one?

	*WAIT!* A serious *BUG*!!!
	We should NOT close the stream before usge in FinishNewMessage.
	Well actualy as of now, FinishNewMEssage only refers to
	the first argument to invoke Close() once.
	But still, in this case, DOUBLE Close()!.
	
	2414   // non-reusable streams will get closed by the store.
	2415   if (reusable)
	2416     destOutputStream->Close();
	2417   return store->FinishNewMessage(destOutputStream, aHdr);
	2418 }

	Change the order of call to FinishNewMessage and Close
	and always close on the caller side.
	     rvfinal = store->FinishNewMessage(destOutputStream, aHdr);
	     rv = destOutputStream->Close();
	     if (NS_FAILED(rv)) {
	     ...
	     maybe we should fail...
	     }
	     return rvfinal;
	     
    mailnews/local/src/nsMsgMaildirStore.cpp (View Hg log or Hg annotations)
        line 704 -- NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!\n");
        line 738 -- NS_ERROR("FinishNewMessage - oops! file does not exist!");
        line 777 -- NS_ERROR("FinishNewMessage - no storeToken in msg hdr!!\n"); 
        line 792 -- NS_ERROR("FinishNewMessage - oops! file does not exist!");
	
    mailnews/local/src/nsPop3Sink.cpp (View Hg log or Hg annotations)
        line 831 -- m_msgStore->FinishNewMessage(m_outFileStream, hdr);

	No close on caller side.
	CAUTION: we should close it on caller side, and not let FinishMessage close
	the stream.

    mailnews/import/test/unit/resources/TestMailImporter.js (View Hg log or Hg annotations)
        line 114 -- msgStore.finishNewMessage(outputStream, newMsgHdr); 

	Caller side clode.
	XXX: No check on the return value of close

Huh, everybody seems to have a perfect and infinte storage, and
network never experiences transient errors. Sigh...



========================================
Re: DisardNewMessage:
----------------------------------------

Current Status : What the Callers of DiscardNewMessage do regarding
the file stream passed as the argument to DiscardNewMessage().

tidied up listing of discardNewMessage  from mxr

Defined as a function prototype in:
mailnews/base/public/nsIMsgPluggableStore.idl (View Hg log or Hg annotations)
	line 146 -- void discardNewMessage(in nsIOutputStream aOutputStream, 

	Spec is not clear about the closing of aOutputStream.
	Probably it should not close within discardNewMessage
	considering the safetey practice of setting stream variable to
	nullptr when stream is closed. Closing should be done outside.

    mailnews/base/util/nsMsgDBFolder.cpp (View Hg log or Hg annotations)
	line 1766 -- msgStore->DiscardNewMessage(m_tempMessageStream, m_offlineHeader); 
	OK: caller side takes care of close.	
	XXX: no check on ->Close...

    mailnews/import/applemail/src/nsAppleMailImport.cpp (View Hg log or Hg annotations)
	line 567 -- msgStore->DiscardNewMessage(outStream, msgHdr); 
	OK: Caller side close
	XXX: no check on -> Close
	XXX/BAD BAD BAD: multiple Closes.

    mailnews/import/eudora/src/nsEudoraMailbox.cpp (View Hg log or Hg annotations)
	line 309 -- msgStore->DiscardNewMessage(outputStream, msgHdr);

	OK: Caller side close
	XXX: no check on -> Close
	XXX/BAD BAD BAD: multiple Closes

		if (NS_SUCCEEDED(rv))
	307           msgStore->FinishNewMessage(outputStream, msgHdr);
	308         else {
	309           msgStore->DiscardNewMessage(outputStream, msgHdr);
	310           IMPORT_LOG0( "*** Error importing message\n");
	311         }
	312 
	313         if (!reusable)
	314           outputStream->Close();

		      should set outputStream to nullptr to avoid double Close()
		      below.
	315 
	316         if (!readBuffer.m_bytesInBuf && (state.offset >= state.size))
	317           break;
	318       }
	319       if (outputStream)
	320         outputStream->Close();

		      should set outputStream to nullptr ?

	line 425 -- msgStore->DiscardNewMessage(outputStream, msgHdr);
	Similar to the code above.

	OK Caller side close
	XXX: no check on -> Close
	XXX/BAD: multiple Closes

    mailnews/import/oexpress/nsOE5File.cpp (View Hg log or Hg annotations)
	line 467 -- msgStore->DiscardNewMessage(outputStream, msgHdr); 

	OK Caller side close
	XXX: no check on -> Close
	XXX/BAD: multiple Closes

    mailnews/import/oexpress/nsOEMailbox.cpp (View Hg log or Hg annotations)
	line 332 -- m_msgStore->DiscardNewMessage(m_dstOutputStream, msgHdr); 

	OK Caller side close
	XXX: no check on -> Close

	The function that invokes DiscardNewMessage()
	bool CMbxScanner::WriteMailItem
	may return without closing when the m_dstOuptuStream is reusable.

	BUG. Inconsistent? File Descriptor Leak?
	XXX: TODO/FIXME should we not close it?

	 bool reusable;
	317 
	318   rv = m_msgStore->GetNewMsgOutputStream(m_dstFolder, getter_AddRefs(msgHdr), &reusable,
	319                                          getter_AddRefs(m_dstOutputStream));
	320   if (NS_FAILED(rv))
	321   {
	322     IMPORT_LOG1( "Mbx getting outputstream error: 0x%lx\n", rv);
	323     return false;
	324   }
	325 
	326   // everything looks kosher...
	327   // the actual message text follows and is values[3] bytes long...
	328   bool copyOK = CopyMbxFileBytes(flags,  values[3]);
	329   if (copyOK)
	330     m_msgStore->FinishNewMessage(m_dstOutputStream, msgHdr);
	331   else {
	332     m_msgStore->DiscardNewMessage(m_dstOutputStream, msgHdr);
	333     IMPORT_LOG0( "Mbx CopyMbxFileBytes failed\n");
	334   }
	335   if (!reusable)
	336   {
	337     m_dstOutputStream->Close();
	338     m_dstOutputStream = nullptr;
	339   }
	340   return copyOK;

	It seems that the code "assumed" that FinishNewMessage and DiscardNewMessage
	will not close the m_dstOutputStream, which is unfortunately untrue.
	We should close it anyway on line 335-338 no matter what reusable value
	is and set m_dstOutputStream to nullptr.


    mailnews/import/outlook/src/nsOutlookMail.cpp (View Hg log or Hg annotations)
	line 431 -- msgStore->DiscardNewMessage(outputStream, msgHdr); 
	OK Caller Side Close
	XXX: No check on Close			     
	XXX: double close

Referenced (in 3 files total) in:

    mailnews/local/src/nsLocalMailFolder.cpp (View Hg log or Hg annotations)
	line 2208 -- mCopyState->m_msgStore->DiscardNewMessage(mCopyState->m_fileStream, 

	OK Caller Side Close 
	XXX: no check on Close();

   (The following file needs change.)
    mailnews/local/src/nsParseMailbox.cpp (View Hg log or Hg annotations)
	line 1823 -- rv = msgStore->DiscardNewMessage(m_outputStream, m_newMsgHdr);
	this is within
	1782 int32_t nsParseNewMailState::PublishMsgHeader(nsIMsgWindow *msgWindow)

	Need CAUTION

	Caller side NO Close?
	XXX: Maybe we can close it on the caller, but I am not sure
	     whether PublishMsgHeader can close it or not...
	     Again what the callers of PublishMsgHeader expect (although
	     as of now, DiscardNewMessage closes m_outputStream anyway.)
	     Move the close on caller side by not having DiscardNewMessage invoke Close.

	line 2562 -- store->DiscardNewMessage(m_outputStream, mailHdr);
	This is within
	2424 nsresult nsParseNewMailState::MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr,
	Caller side NO Close?
	The same comment as above.
	XXX:  Move the close on caller side by not having DiscardNewMessage invoke Close.

    mailnews/local/src/nsPop3Sink.cpp (View Hg log or Hg annotations)
	line 862 -- m_msgStore->DiscardNewMessage(m_outFileStream,
	Within IncorporateAbort

	Need CAUTION

	Caller side no close
	XXX: may close on the caller side. 

	XXX: TODO/FIXME
	===> Do not let DiscardNewMessage close the stream (its 1st argumen)
	and let the caller side to handle the close.

[AGAIN, after the analysis above, I came up with the proposal that
DiscardNewMessage should NOT close the first file argument, and
the caller should close it if necessary.




========================================

A several places in code that may need extra changes.


 NS_IMPL_ISUPPORTS(nsPop3Sink, nsIPop3Sink)
 
 nsPop3Sink::nsPop3Sink()
 {
     m_authed = false;
     m_downloadingToTempFile = false;
     m_biffState = 0;
     m_numNewMessages = 0;
@@ -501,16 +697,17 @@ nsPop3Sink::IncorporateBegin(const char*
                                       &reusable, getter_AddRefs(m_outFileStream));
   }
   // The following (!m_outFileStream etc) was added to make sure that we don't
   // write somewhere where for some reason or another we can't write to and
   // lose the messages. See bug 62480
   if (!m_outFileStream)
       return NS_ERROR_OUT_OF_MEMORY;
 
+  // ??? XXX: Following is no longer used? Strange??? TODO/FIXME: check out why
   nsCOMPtr<nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream);
 
   // create a new mail parser
   if (!m_newMailParser)
     m_newMailParser = new nsParseNewMailState;
   NS_ENSURE_TRUE(m_newMailParser, NS_ERROR_OUT_OF_MEMORY);
   if (m_uidlDownload)
     m_newMailParser->DisableFilters();
@@ -676,16 +873,19 @@ nsresult nsPop3Sink::WriteLineToMailbox(
     // The following (!m_outFileStream etc) was added to make sure that we don't write somewhere
     // where for some reason or another we can't write to and lose the messages
     // See bug 62480
     NS_ENSURE_TRUE(m_outFileStream, NS_ERROR_OUT_OF_MEMORY);
 
     // seek to the end in case someone else has seeked elsewhere in our stream.
     nsCOMPtr <nsISeekableStream> seekableOutStream = do_QueryInterface(m_outFileStream);
     seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+
+    // XXX: what happens if the thread is interrupted here?
+
     uint32_t bytesWritten;
     m_outFileStream->Write(buffer.BeginReading(), bufferLen, &bytesWritten);
     NS_ENSURE_TRUE(bytesWritten == bufferLen, NS_ERROR_FAILURE);
   }
   return NS_OK;
 }
 
 nsresult nsPop3Sink::HandleTempDownloadFailed(nsIMsgWindow *msgWindow)
@@ -728,8 +928,17 @@ nsresult nsPop3Sink::HandleTempDownloadF
     m_newMailParser->m_newMsgHdr = nullptr;
 
     return (dlgResult == 0) ? NS_OK : NS_MSG_ERROR_COPYING_FROM_TMP_DOWNLOAD;
   }
   return rv;
 }
 
 
+//
+// nsPop3Sink::IncorporateComplete(nsIMsgWindow *aMsgWindow, int32_t aSize)
+//
+// m_outFileStream should still be open if !m_downloadingToTempFile
+// it is closed if (m_downloadingToTempFile.)
+//
+
+// XXX: something is wrong(?): m_outFileStream->Close() is called
+// twice in a path.

+// Essentially (less error check) the code looks like this.

+//
+//      m_outFileStream->Close();
+//      m_newMailParser->FinishHeader();
+//      ...
+//      nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream);
+//      rv = MsgReopenFileStream(m_tmpDownloadFile, inboxInputStream);
+//      // Should this reference be inboxInputStream???
+//      if (m_outFileStream)
+//      {
+//        int64_t tmpDownloadFileSize;
+//        uint32_t msgSize;
+//        hdr->GetMessageSize(&msgSize);
+//        nsCOMPtr <nsIFile> tmpClone;
+//        rv = m_tmpDownloadFile->Clone(getter_AddRefs(tmpClone));
+//        tmpClone->GetFileSize(&tmpDownloadFileSize);
+//        ...
+//        rv = m_newMailParser->AppendMsgFromStream(inboxInputStream, hdr,
+//                                                    msgSize, m_folder);
+//        // XXX: DANGER, something is wrong???
+//        // We have ALREADY closed m_outFileStream  about 32 lines above!!!
+//
+//        // But we have duplicated some info into inboxInputStream
+//        // Maybe we should be closing inboxInputStream here???
+//
+//        m_outFileStream->Close(); // close so we can truncate.
+//        m_tmpDownloadFile->SetFileSize(0);
+
+//
+//
 NS_IMETHODIMP
 nsPop3Sink::IncorporateComplete(nsIMsgWindow *aMsgWindow, int32_t aSize)
 {
   if (m_buildMessageUri && !m_baseMessageUri.IsEmpty() && m_newMailParser &&
       m_newMailParser->m_newMsgHdr)
   {
     nsMsgKey msgKey;
     m_newMailParser->m_newMsgHdr->GetMessageKey(&msgKey);
@@ -780,28 +1019,30 @@ nsPop3Sink::IncorporateComplete(nsIMsgWi
       rv = GetMsgDBHdrFromURI(m_origMessageUri.get(), getter_AddRefs(oldMsgHdr));
       if (NS_SUCCEEDED(rv) && oldMsgHdr)
         localFolder->UpdateNewMsgHdr(oldMsgHdr, hdr);
     }
 
     if (m_downloadingToTempFile)
     {
       // close file to give virus checkers a chance to do their thing...
-      m_outFileStream->Flush();
+      m_outFileStream->Flush(); // don't need this, do we?
       m_outFileStream->Close();
       m_newMailParser->FinishHeader();
       // need to re-open the inbox file stream.
       bool exists;
       m_tmpDownloadFile->Exists(&exists);
       if (!exists)
         return HandleTempDownloadFailed(aMsgWindow);
 
       nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream);
       rv = MsgReopenFileStream(m_tmpDownloadFile, inboxInputStream);
       NS_ENSURE_SUCCESS(rv, HandleTempDownloadFailed(aMsgWindow));
+
+      // Should this reference be inboxInputStream???
       if (m_outFileStream)
       {
         int64_t tmpDownloadFileSize;
         uint32_t msgSize;
         hdr->GetMessageSize(&msgSize);
         // we need to clone because nsLocalFileUnix caches its stat result,
         // so it doesn't realize the file has changed size.
         nsCOMPtr <nsIFile> tmpClone;
@@ -812,16 +1053,22 @@ nsPop3Sink::IncorporateComplete(nsIMsgWi
         if (msgSize > tmpDownloadFileSize)
           rv = NS_MSG_ERROR_WRITING_MAIL_FOLDER;
         else
           rv = m_newMailParser->AppendMsgFromStream(inboxInputStream, hdr,
                                                     msgSize, m_folder);
         if (NS_FAILED(rv))
           return HandleTempDownloadFailed(aMsgWindow);
 
+        // XXX: DANGER, something is wrong???
+        // We have ALREADY closed m_outFileStream  about 32 lines above!!!
+
+        // But we have duplicated some info into inboxInputStream
+        // Maybe we should be closing inboxInputStream here???
+
         m_outFileStream->Close(); // close so we can truncate.
         m_tmpDownloadFile->SetFileSize(0);
       }
       else
       {
           return HandleTempDownloadFailed(aMsgWindow);
         // need to give an error here.
       }
@@ -846,21 +1093,30 @@ nsPop3Sink::IncorporateComplete(nsIMsgWi
   printf("Incorporate message complete.\n");
 #endif
   nsCOMPtr<nsIPop3Service> pop3Service(do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
   pop3Service->NotifyDownloadProgress(m_folder, ++m_numMsgsDownloaded, m_numNewMessages);
   return NS_OK;
 }
 
+// Incorporate Abort
+//
+// m_outFileStream is closed always
+// 
 NS_IMETHODIMP
 nsPop3Sink::IncorporateAbort(bool uidlDownload)
 {
  // In IncorporateComplete we call m_FileStream->Close.
  // Maybe the following m_outFileStream->Close()
  // should come AFTER *the* if statement? 
  //
   nsresult rv = m_outFileStream->Close();
   NS_ENSURE_SUCCESS(rv,rv);
+
   if (!m_downloadingToTempFile && m_msgStore && m_newMailParser &&
       m_newMailParser->m_newMsgHdr)
   {
       m_msgStore->DiscardNewMessage(m_outFileStream,
                                     m_newMailParser->m_newMsgHdr);
   }
 #ifdef DEBUG
   printf("Incorporate message abort.\n");

----
Depends on: 1122698
Component: Untriaged → General
Keywords: meta
Component: General → Networking: POP
Product: Thunderbird → MailNews Core
Depends on: 1134527
Assignee: nobody → ishikawa
Depends on: 1134529
Step 1 is addressed by Bug 1122698.
Step 2 is addressed by Bug 1134527.
Step 3 is addressed by Bug 1134529.
(In reply to ISHIKAWA, Chiaki from comment #2)
> Step 1 is addressed by Bug 1122698.
> Step 2 is addressed by Bug 1134527.
> Step 3 is addressed by Bug 1134529.

step-5 is 
Bug 1116055 - Performance issue: Failure to use buffered write (comm-central thunderbird)

So actually step-5 will come before step-4.
Depends on: 1116055
Blocks: 1174500
(In reply to ISHIKAWA, Chiaki from comment #3)
> (In reply to ISHIKAWA, Chiaki from comment #2)
> > Step 1 is addressed by Bug 1122698.
> > Step 2 is addressed by Bug 1134527.
> > Step 3 is addressed by Bug 1134529.
> 
> step-5 is 
> Bug 1116055 - Performance issue: Failure to use buffered write (comm-central
> thunderbird)
> 
> So actually step-5 will come before step-4.

I re-arranged a few things and entered a few bugzilla entries.
This is my plan.

========================================
PLANNED ORDER
========================================

* 1 A check_fileptr_change.patch: Bug 1116055: Check the unexpected change of file seek position and ask the user to report it

   bug 1116055 

   This was considered a necessary step so that we can know that if
   any user experiences a strange seek position change.

* 2 A fix-close-step-1.patch: Bug 1122698 Cleaning of incorrect Close, unchecked Flush, Write etc. in nsPop3Sink.cpp and friends (part 1 of  a series)

   bug 1122698

* 3 A fix-close-part-2.patch: Bug 1134527 Add error value checking of Close() and Flush() (part-2 of a series)

   bug 1134527

* 4 A fix-close-part-3-write-error-check.patch: Bug 1134529: Check Write failures (part-3 of a series)

   bug 1134529

* 5 A removing-a-Seek.patch: C-C Thunderbird - Cleaning of incorrect Close, unchecked Flush, Write etc. in nsPop3Sink.cpp and friends. (step 4)

   bug 1174500 [was mistyped as 1117450 in a few bugzilla entries. :-( ]

6 A 1116055-acelist.patch: One liner patch posted in Bug 1116055 to return a buffered stream from mbox::getNewOutputStream by aceman

   Bug 1176857 - C-C TB Enabling buffering for pop3 download (and possibly part of imap file operation)

   I have created a separate bug for this since it will be necessary
   to apply this patch only after other patches landed. (This is
   because enabling the buffering requires proper error checking of
   |Close()| and |Flush()|. These are only taken care of AFTER 1-5
   patches above are applied.)

   Maybe about a month after the first patch (bug 1116055) above is
   applied, and other patches 2-5 have been applied, I can enable this
   patch. (Assuming there is NO ONE who reports strange seek position
   change on their computer.)

7 A add-short-read-handling.patch: Bug 1170606 - C-C T-B fixes to take care of short read

   Bug 1170606

   This is theoretically independent of 1-6, but my local patch
   assumes the changes in 1-6 to be in place. (The patch may not apply
   cleanly without 1-6 in advance.)

  There are some additional shor-read issues UNDER M-C portion of the
  source tree.  They can be applied indepdent of this series.

Please feel free to take up the review or move it to somebody else
based on the workload of the potential reviewers.

TIA
No longer blocks: 1174500
Depends on: 1174500
Blocks: 454359
The design of not closing the passed stream inside FinishNewMessage and
DiscardNewMessage had to be changed a little.
The version of FinishNewMessage and DiscardNewMessage for Maildir storage format
(there are ones for imap and pop3 local storage, too) 
need to close the stream to cope with the issue of the failure of renaming/removing a file when an open file descriptor exists for that file UNDER WINDOWS.

Here is a short write up I wrote to explain the background of this change.

I have finally resolved the issues with my patch set, and
Windows mozmill and xpcshell tests on try-comm-central run without errors!

https://treeherder.mozilla.org/#/jobs?repo=try-comm-central&revision=ba9d63b23951

History of the final bugs that kept me from asking for formal reviews:

The errors occurred only with Maildir usage.
It was rather difficult to diagnose the issues that happen only under Windows since
I rely try-comm-central build and test for debugging.

After checking the return code of various calls leading up to the
errors, and dumping file pathnames involved in errors,
I finally figured that most of the errors seemed to be failures of
copying a message file from a directory to the other for Maildir operation.
I could not figure out whether the file to be copied was finally there or not.
So I finally bit the bullet and inserted code to dump directory listing under windows to see if the message files are there as they are supposed to be.
(I basically called "dir filepath", "dir directorypath" to obtain the listing.)

With the new dump listing, I found that the message files WERE there.
And so I was motivated to learn further WHERE the error to copy/move occurred. 
The further detailed error check showed that CopySingleFile() failed
to move the message file from tmp to cur directory or something like that.

Coupled with some strange errors about NS_ERROR_FILE_IS_LOCKED which I saw earlier,
I could only think that there are open file descriptors to the message files,
which prevented TB from copying/moving the message files UNDER WINDOWS ONLY.
(Under linux and OSX, and other OSes that have UNIX UFS-like filesystem semantics, we can move/copy/rename files even if there are open file descriptors to the file.
But Windows does not allow us to do that.)

So I re-read my patch set carefully and considering that
bisecting only indicated that very fundamental (i.e., early) part of my patch set was to blame, I finally figured that my NOT closing file stream
in FinishNewMessage(), especially the Maildir version in nsMsgMaildirStore.cpp,
was to blame. 
FinishNewMessage() for Maildir operation tries to
move the freshly downloaded/inserted message to a new location at the end.
And it seemed that the closing of the passed file stream is essential for Maildir.
So I tweaked the code to close the stream ONLY IN the version of FinishNewMessage and
DiscardNewMessage() in nsMsgMaildirStore.cpp.
I need to close the file stream in DiscardNewMessage() since it tries to REMOVE the message file.

OTOH, I want to handle the Close() of the file stream to the newly downloaded/copied/moved message file (or mbox, etc.) on the caller's side
so that yet-to-be-devised better error-handling could be done in the
logically higher-level of routines (those that call FinishNewMessage or DiscardNewMessage).
So how to fuse the dichotomy of different versions?
I decided to add a third argument to the pair of functions to return the indicator whether these functions closed the passed file stream (as the 1st argument) before returning.
By looking at the indicator, the callers can decide whether to close the file stream
and check the error code on their own.

With the change, Windows DEBUG build on try-comm-central ran without a hitch!
I can tell.

I will post a summary of the new patch and revised roadmap to bug 1116055.

TIA
Depends on: 1306914
No longer depends on: 1306914
See Also: → 1116055
See Also: → 484119
This bug is now being worked on
bug 1242030 after a few incarnations.
Depends on: 1242030
You need to log in before you can comment on or make changes to this bug.