| 818 | |
| 819 | |
| 820 | bool CPartFile::SavePartFile(bool Initial) |
| 821 | { |
| 822 | switch (status) { |
| 823 | case PS_WAITING_FOR_HASH: |
| 824 | case PS_HASHING: |
| 825 | case PS_COMPLETE: |
| 826 | return false; |
| 827 | } |
| 828 | |
| 829 | /* Don't write anything to disk if less than 100 KB of free space is left. */ |
| 830 | sint64 free = CPath::GetFreeSpaceAt(GetFilePath()); |
| 831 | if ((free != wxInvalidOffset) && (free < (100 * 1024))) { |
| 832 | return false; |
| 833 | } |
| 834 | |
| 835 | // Atomic-rename save. |
| 836 | // |
| 837 | // Old sequence (per partfile, every save tick): |
| 838 | // 1. CPath::BackupFile(m_fullname, ".backup") -- full content copy |
| 839 | // of .part.met into .part.met.backup as an in-flight intermediate. |
| 840 | // 2. CPath::RemoveFile(m_fullname). |
| 841 | // 3. Write new .part.met from scratch. |
| 842 | // 4. CPath::RemoveFile(".backup") on success. |
| 843 | // 5. CPath::BackupFile(m_fullname, PARTMET_BAK_EXT) -- full content |
| 844 | // copy of the freshly-written .part.met into .part.met.bak as the |
| 845 | // long-term recovery backup. |
| 846 | // |
| 847 | // Three full-size content copies per save plus a delete/create dance on |
| 848 | // the live .part.met. On a sharer with hundreds of dirty partfiles per |
| 849 | // timer tick the aggregate runs the main thread continuously through |
| 850 | // disk I/O (see issue #669) -- write() syscalls are microseconds each, |
| 851 | // but the loop length itself is the bottleneck. |
| 852 | // |
| 853 | // New sequence: |
| 854 | // 1. Write new content into .part.met.tmp. |
| 855 | // 2. rename(.part.met, .part.met.bak) -- O(1) metadata op, promotes |
| 856 | // the previous .part.met to long-term backup. |
| 857 | // 3. rename(.part.met.tmp, .part.met) -- atomic install of new |
| 858 | // content, POSIX rename guarantees the target is either fully |
| 859 | // old-content or fully new-content at every observable moment. |
| 860 | // |
| 861 | // One content write plus two metadata renames. ~3x reduction in |
| 862 | // per-save disk work, and stronger crash safety because the live |
| 863 | // .part.met is never absent or partial. |
| 864 | const CPath tmpName = m_fullname.AppendExt(".tmp"); |
| 865 | const CPath bakName = m_fullname.AppendExt(PARTMET_BAK_EXT); |
| 866 | |
| 867 | CFile file; |
| 868 | try { |
| 869 | if (!m_PartPath.FileExists()) { |
| 870 | throw wxString(".part file not found"); |
| 871 | } |
| 872 | |
| 873 | uint32 lsc = lastseencomplete; |
| 874 | |
| 875 | file.Open(tmpName, CFile::write); |
| 876 | if (!file.IsOpened()) { |
| 877 | throw wxString("Failed to open part.met.tmp file"); |
no test coverage detected