File Descriptor泄漏導致Crash: Too many open files,descriptorcrash

來源:互聯網
上載者:User

[轉]File Descriptor泄漏導致Crash: Too many open files,descriptorcrash

在實際的Android開發過程中,我們遇到了一些奇奇怪怪的Crash,通過sigaction再配合libcorkscrew以及一些第三方的Crash Reporter都捕獲不到發生Crash的具體資訊,十分頭疼。然後我們通過Bugly上報的Java的CallStack觀察發現這些Crash發現了一些共同的資訊:

看來是和OpenGL有關係,於是我們進一步對程式輸出的log進行觀察,又發現:

從這個log裡面我們獲得了幾個資訊:

我們對我們已有的裝置反覆實驗,確實了只有Adreno的裝置(小米3,HTC M8,華為P7等)會在特定條件下出現這種奇奇怪怪的隨機Crash。而其他裝置例如小米Pad(Tegra),三星S3(Mali)等都不會出現這種問題。這個問題確實頭疼,在網上搜尋了很久也沒找到有用的資訊。直到在某次小米3上再次測試的時候,發現了log裡面還有一條必然出現的資訊:

E/MemoryHeapBase(18703): error creating ashmem region: Too many open files

這個資訊間接的指出了問題,也給了我們一些提示:似乎開啟了過多的檔案。於是靠著這個靈光,我們嘗試著在程式中輸出所有已開啟的檔案:

 SHOW FILE HANDLES: 0  (socket:[285038]): read-write 1  (/dev/null): read-write 2  (/dev/null): read-write 3  (/dev/log/main): cloexec write-only 4  (/dev/log/radio): cloexec write-only 5  (/dev/log/events): cloexec write-only 6  (/dev/log/system): cloexec write-only 7  (/sys/kernel/debug/tracing/trace_marker): write-only 8  (/dev/__properties__): 9  (/dev/binder): cloexec read-write10  (/dev/log/main): cloexec write-only11  (/dev/log/radio): cloexec write-only12  (/dev/log/events): cloexec write-only13  (/dev/log/system): cloexec write-only14  (/system/framework/framework-res.apk):15  (/system/framework/core-libart.jar):16  (pipe:[282578]): nonblock17  (/dev/alarm):18  (/dev/cpuctl/tasks): cloexec write-only19  (/dev/cpuctl/bg_non_interactive/tasks): cloexec write-only20  (socket:[282569]): read-write21  (pipe:[282570]):22  (pipe:[282570]): write-only23  (pipe:[282578]): nonblock write-only24  (anon_inode:[eventpoll]): read-write25  (/data/app/---app_name---/base.apk):26  (/data/data/---app_name---/databases/bugly_db): cloexec read-write27  (socket:[285047]): read-write28  (anon_inode:mali-8938): cloexec29  (socket:[282605]): nonblock read-write30  (socket:[283605]): nonblock read-write31  (/dev/null): read-write32  (/dev/ump): read-write33  (socket:[285045]): nonblock read-write34  (/dev/null): read-write35  (/dev/mali): read-write36  (anon_inode:mali-8938): cloexec37  (anon_inode:mali-8938): cloexec38  (/data/app/---app_name---/base.apk):39  (anon_inode:mali-8938): cloexec40  (anon_inode:mali-8938): cloexec41  (/dev/null): read-write42  (/dev/null): read-write43  (/data/app/---app_name---/base.apk):44  (/dev/null): read-write45  (anon_inode:mali-8938): cloexec46  (/data/data/---app_name---/files/DefaultFont.ttf):47  (/data/app/---app_name---/base.apk):48  (anon_inode:sync_fence):49  (/dev/null): read-write50  (socket:[285060]): cloexec read-write52  (anon_inode:mali-8938): cloexec53  (anon_inode:mali-8938): cloexec54  (/dev/null): read-write55  (anon_inode:sync_fence):56  (pipe:[284134]): write-only58  (anon_inode:sync_fence):62  (anon_inode:sync_fence):63  (anon_inode:sync_fence):

通過不停測試程式,發現已開啟的檔案數量一直有增無減,而當這些被開啟的檔案數量接近1024的時候,上面的eglSwapBuffers必然出錯。於是乎我們得出一個中間結論:

  • 如果程式開啟的檔案數量過多,會導致OpenGL swap buffer失敗!

這從字面上看著似乎有些扯淡,因為這兩者總感覺沒啥聯絡。這個問題只會出現在Adreno的GPU上面,於是我們猜想:

  • Adreno的驅動在swap buffer的時候,需要申請新的FD,這個FD可能是某些硬體IO,具體不得而知;
  • 如果程式中其他的各種FD使用過多接近上限,會導致Adreno的驅動申請不到必要的FD,因此導致swap buffer失敗。

這樣看起來似乎就比較有道理了。雖然sawp buffer本身是不會Crash的,他並沒有raise任何signal,只是簡單的返回了一個錯誤的結果,但這會導致上層邏輯出現異常。這些異常在不同的裝置上表現不一樣:

  • 有的裝置會在Java層的eglSwapBuffers觸發Java層的Exception導致Crash;
  • 有的裝置不會出現異常,但是會導致OpenGL停止工作(halt rendering),其表現結果就是程式卡住無響應;
  • 有的裝置可能什麼都不會發生,但是如果你的互動觸發了其他邏輯:比如按回退鍵彈出對話方塊,對話方塊也需要FD,但是獲得不到,那麼彈出對話方塊的邏輯將拋出異常,

於是這就有了各種奇奇怪怪的Crash。

解決方案

通過對代碼的排查,我們發現在使用SoundPool處理音效的時候,確實存在FD泄露的情況:

1  private SoundPool m_soundPool;2  public int loadSound(String path) {3      int soundID = m_soundPool.load(getAssets().openFd(path), 0);4      return soundID;5  }6  public unloadSound(int soundID) {7      m_soundPool.unload(soundID);8  }

雖然我們在不需要這些音效的時候,對其進行了卸載處理,但不知道是SoundPool類自身的缺陷,還是我們的使用不當,在實際測試中我們發現unload過後,在load中通過openFd開啟的FD並沒有被釋放掉。強制調用System.gc()在一些裝置(例如小米3)上可以釋放掉這部分FD,但是另一些裝置(例如HTC M8)即使強制gc這無法卸載掉它們,於是便出現了FD泄露的情況。

最終我們自行對這些FD進行管理,並且在unload的時候手動調用這些FD的close方法:

 1  private SoundPool m_soundPool; 2  private HashMap<Integer, AssetFileDescriptor> m_soundFdMap 3  public int loadSound(String path) { 4      AssetFileDescriptor fd = getAssets().openFd(path); 5      int soundID = m_soundPool.load(fd, 0); 6      m_soundFdMap.put(soundID, fd); 7      return soundID; 8  } 9  public unloadSound(int soundID) {10     m_soundPool.unload(soundID);11     m_soundFdMap.get(soundID).close();12  }

這之後FD再無泄露的情況發生,之前的各種裝置上面的各種奇奇怪怪的Crash都被處理好了。

小結

這個問題粗略說起來就是:因為播放了太多的音效,導致Adreno底層渲染失敗,以至於上層邏輯各種失措,產生了很多奇奇怪怪的Crash。準確的解釋應該是:程式中的FD泄露如同記憶體泄露一樣是同樣需要得到關注的問題,FD的耗盡如同記憶體的耗盡一樣會導致程式的各種異常情況發生,但是前者不如後者那麼知名也不如後者容易被察覺。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.