Open Bug 1121842 Opened 10 years ago Updated 7 years 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
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.