Safari에서 Indexed DB의 IDBCursor.onsuccess 인수 메모리 문제

개요



Safari에서 Indexed DB를 사용할 때 IDBCursor의 onsuccess 콜백 인수 속성 target.result를 참조하면 null이 아닌 경우 특수 메모리 누수가 발생했습니다.
상기 조건하에서 브라우저 페이지를 리로드했을 경우, javascript 오브젝트가 좀비화해 메모리 영역에 남아 계속한다.
객체는 재로드 횟수에 따라 계속 증가하기 때문에 메모리 압축에 의한 브라우저 페이지 충돌을 일으킬 수 있습니다.
Chrome에서는 이 현상을 확인할 수 없었다.

이 현상은 이미 Apple Bug Reporter를 통해보고되었습니다.

현상을 확인한 환경


  • maxOS 10.12.6/Safari 11.1.1
  • iOS 10.3/Safari
  • iOS 11.4/Safari

  • 검증



    검증을 위한 샘플 코드.
    // 正常に indexed db が接続できているとする
    var transaction = openRequest.result.transaction([storeName], 'readwrite');
    var store = transaction.objectStore(storeName);
    
    // 何かレコードを保存していないと、後述のプロパティが null となる
    store.put(key, 1);
    
    store.openCursor().onsuccess = function(e) {
    
        // 1. e.target.result が null の場合
        // var result = e.target.result;
    
        // 2. e.target.result が null ではない場合
        // var result = e.target.result;
    
        // 3. e.target の参照のみ行う
        // var target = e.target;
    
        // 4. e.target の参照のみだが関数内で展開
        // console.log(e.target);
    
    };
    

    결과




    case
    결과


    1. e.target.result가 null의 경우
    리로드로 인한 누출은 발생하지 않음

    2. e.target.result가 null가 아닌 경우
    재장전으로 누출

    3. e.target 참조만 수행
    리로드로 인한 누출은 발생하지 않음

    4. e.target 참조 만 함수 내에서 확장
    재장전으로 누출


    고찰



    통상의 Web 페이지/서비스라면에 대해서 문제가 되기 어렵지만, 큰 바이너리등을 취급하는 페이지의 경우는 크래쉬의 리스크가 높다.
    메모리 해제 수단으로서 페이지의 리로드가 행해지는 경우도 있지만, 이 문제가 발생하는 조건 하에서는 역효과이다.

    대응 방법 (잠정)



    커서의 용도는 몇 가지 있지만, 모든 레코드를 반복할 가능성이 있는 처리를 실시한다고 가정하면, getAllKeysgetAll 가 대체가 된다.getAll 등은 indexeddb2 의 사양이며, 비교적 새로운 버젼의 브라우저로만 서포트되고 있다.

    W3C
    htps //w w. w3. rg / TR / 어서 dB-2 /
    caniuse
    htps // 게으세요. 코 m / # 훗아 t = 어서 db2

    대응 예 1


    var allKeys;
    var allValues;
    
    objectStore.getAllKeys().onsuccess = (event) => {
        allKeys = event.target.result;
    };
    objectStore.getAll().onsuccess = (event) => {
        allValues = event.target.result;
    };
    
    ...
    
    for (var i = 0; i < allKeys.length; i++) {
        const key = allKeys[i];
        const val = allValues[i];
        doSomething(key, value);
    }
    

    이 대응 문제


  • 모든 키와 값이 메모리에 올라갑니다
  • getAllKeysgetAll의 결과 ( event.target.result )의 순서가 보장된다고 가정합니다

  • 대응 예 2


    var allKeys;
    objectStore.getAllKeys().onsuccess = (event) => {
        allKeys = event.target.result;
    };
    
    ...
    
    for (var i = 0; i < allKeys.length; i++) {
        const key = allKeys[i];
        objectStore.get(key).onsuccess = (event) => {
            const value = request.result;
            doSomething(key, value);
        };
    }
    

    이 대응 문제


  • 모든 키가 메모리에 올라갑니다
  • 키마다 DB를 읽으므로 성능면에서 우려가 많다



  • 처음 보았을 때는 끌었다.
    window는 좋지만, 그 아래의 ArrayBuffer, AudioBuffer가 위험하다.

    좋은 웹페이지 즐겨찾기