Пример утечки памяти в indexedDB в store.add (см. Пример в Edit) - PullRequest
0 голосов
/ 29 мая 2018

Я пытаюсь понять, почему этот код генерирует утечку памяти или, по крайней мере, то, что кажется утечкой памяти.

Я пытаюсь записать портфель объектов викторины в базу данных indexedDB.На данном этапе я просто пытаюсь проверить скорость написания разного количества тестов разного размера.

Включена только часть кода, которая пишет в базу данных.Объект port.quiz [] определяется глобально.Код настроен для отображения сообщений, когда каждый тест успешно написан, и окончательного сообщения, когда полная транзакция завершена.

Часть, которую я не смог понять, - это причина, по которой утверждение «удалить port.quiz»[code [c]] »в блоке request.onsuccess функции store_data требуется, чтобы избежать утечки памяти.Если это закомментировано, использование памяти увеличивается по мере написания каждого теста и не освобождается в конце транзакции.

Если оператор удаления включен, то использование памяти увеличивается для четырех или пяти хранилищ тестов.а затем падает вниз;но максимальная сумма увеличивается до каждой капли.При закрытии транзакции использование памяти возвращается к тому, что было до вызова функции write_quizzes.

Тестирование несколько нелепо в том смысле, что я пишу 100 тестов, каждый из которых содержит 500 вопросов, иКомпоненты каждого вопроса заполнены большим количеством текста, чем кто-либо мог бы использовать.Но я пытаюсь проверить более чем разумный ожидаемый максимальный предел.Помимо утечки памяти, он работает довольно хорошо.

Если использование памяти просматривается в диспетчере задач Windows, оно начинается примерно с 2 МБ и увеличивается до 2 ГБ к 100-му опросу, если оператор delete port.quiz имеет значениене включено.Если оператор включен, использование памяти возрастает до 5–6 МБ, а затем уменьшается до 4 МБ, затем увеличивается до 7–8 МБ, затем уменьшается до 5 МБ и т. Д., Пока не достигнет максимального значения около 1,1–1,2 ГБ, а затем не уменьшится до2 МБ при закрытии транзакции.

Я подумал, что, возможно, проблема была такой, как описано в этих двух ссылках, но я не смог ее выяснить.

https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/

https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156

Я попытался переместить функцию store_data за пределы функции write_quizzes, если проблема связана с общей областью действия;и это, кажется, уменьшает максимальное достигнутое использование памяти, но такое же поведение в целом существует.

Я также столкнулся с проблемой, отмеченной для браузера Edge в отношении indexedDB, приводящей к утечке памяти;но я тестировал только в Firefox, и в ссылке все равно нет решения.

https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7122372/

Даже с оператором delete использование памяти слишком велико.Объект, содержащий 100 тестов, составляет около 2 МБ.Итак, почему использование памяти так сильно увеличивается с написанием каждого отдельного теста, меня смущает.Возможно, я просто упускаю из виду нечто очевидное и фундаментальное.

Спасибо за любые рекомендации или предложения, которые вы можете предоставить.

   function write_quizzes() { 


       // Separating the transaction from the objectStore allows for
       // separate onsuccess and onerror events.

       var qz_tran = db.transaction( ["quiz data"], "readwrite" ), 

           store = qz_tran.objectStore("quiz data"),

           code = new Array(),

           i = 0, q, l, 

           request;


      // Do something when all the data is added to the database.

      qz_tran.oncomplete = function(event) {

           $("#msg").text('Finished writing the ' + i + ' quizzes to database!');

      }; // close oncomplete


      qz_tran.onerror = function(event) {

        // Don't forget to handle errors!

           alert("Error writing data " + event.target.errorCode + " .");

      }; // close onerror



      // Writing quiz object references to an array in order to write recursively.

      for ( q in port.quiz ) { 

           code[++i] = q;

      }; // next q


      l = code.length;  

      store_data(1);



      function store_data(c) {

           request = store.add( port.quiz[ code[c] ], code[c] );

           request.onsuccess = function( event ) { 

                $('#msg').text('Writing quiz ' + code[c]);

                delete port.quiz[ code[c] ]; 

                // Why does this need to be deleted and how is it leaking memory?

                if ( c + 1 < l ) { store_data(++c); };

           }; // close onsuccess


           request.onerror = function( event ) { 

              alert('write error : '  + event.target.errorCode ); 

           }; // close onerror


       } // close store_data

РЕДАКТИРОВАТЬ с ПРИМЕРОМ:

Ниже приведен HTML-код сскрипт, который демонстрирует возникшую проблему.Если нажать кнопку «Построить портфолио», он создаст примерный портфель данных нежелательной викторины и откроет базу данных с одним хранилищем объектов на уровне викторины.После того, как в сообщении говорится, что база данных была открыта, при каждом нажатии кнопки «Написать тест» записывается один из пятидесяти объектов викторины из портфолио в хранилище объектов.Если наблюдать за использованием памяти, даже в диспетчере задач Windows, он будет увеличиваться с каждым щелчком после сообщения «Завершена запись теста в базу данных» и не будет освобожден после перезагрузки страницы.

Я экспериментировалв течение нескольких часов и одной пробной версии я заметил, что store.put для существующего ключа теста увеличит использование памяти на секунду или около того, а затем вернется к уровню до щелчка;но для нового ключа put занял бы память и сохранил ее так же, как store.add здесь.

Я в значительной степени новичок в indexedDB, но могу только заключить, что проблема связана с store.add или потому, что объект портфолио является глобальным.Я признаю, что не понимаю информацию в веб-документах MDN о store.add, создающем структурированный клон и сериализацию значения, но задаюсь вопросом, не освобождает ли что-то на этом этапе память.

https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add

https://html.spec.whatwg.org/multipage/structured-data.html#structured-clone

Эти две ссылки имеют отчетные даты с 2015 года, но обновляются только 29 дней назад;и проблема, которую они описывают, особенно в том, что она возникает на больших объектах, а не на меньших, похоже, похожа на эту ситуацию, так как я пытался проверить написание нескольких больших объектов викторины.Или, может быть, мои контрольные примеры немного нереалистичны для одного объекта, а размер теста слишком велик.

Состояние гонки может привести к избыточным операциям синхронизации хранилища IDBFS.https://github.com/kripken/emscripten/issues/3908

Erratic IndexedDB большие операции с хранилищем файлов, так как IndexedDB потребляет большой объем памяти, который не освобождается.https://bugzilla.mozilla.org/show_bug.cgi?id=1223782

Также связанные и более свежие, и еще предстоит ответить ..

Indexeddb, кажется, не свободная память

Интересно, еслиЦикл k в функции gen_port полностью закомментирован, и то, что было текстовым свойством объектов answer_choice (переменная t = 5000 вопросительных знаков), увеличивается в 26 раз до 130 000 вопросительных знаков и записывается как единственный элемент данных каждогообъект вопроса, так что каждый объект вопроса по-прежнему содержит примерно тот же объем данных, который был бы при сборке из 26 отдельных объектов с выбором ответа, сгенерированных в цикле k, утечки памяти не происходит.(См. Gen_port_2 в приведенном ниже примере кода.) Это происходит только при наличии уровня объекта выбора ответа: portfolio.quiz []. Question []. Answer_choice []. Text.Использование памяти увеличивается при записи каждого теста в базу данных, но после завершения возвращается к уровню предварительной записи.

Это наводит меня на мысль, что проблема очень сильно связана с проблемой ссылки на bugzilla вышеозаглавленный « Erratic IndexedDB большие операции с хранилищем файлов, так как IndexedDB потребляет большой объем памяти, который не освобождается ».Возможно, исправление проблемы большого двоичного объекта не решит эту проблему, когда большой двоичный объект представляет собой большой кусок данных без уровней вложенных объектов, которые требуют сериализации в базе данных.Утечка памяти наблюдается в диспетчере задач Windows в firefox.exe, как ссылка, впервые описанная три года назад.

Как я уже писал ранее, я очень новичок в этой области и могу сильно запутаться,Я также не знаком с bugzilla и могу ли я добавить это в их группу, но я буду исследовать.Возможно, мой пример - неправильное использование того, что indexedDB намеревается предоставить;но, возможно, нет.

Для моих конкретных целей, я думаю, я могу просто привести в порядок объект уровня викторины перед записью в базу данных.Я просто хочу хранить и извлекать тесты в виде блока и не требовать шага сериализации для поиска или запроса базы данных по отдельным элементам данных в объектах викторины.Итак, я думаю, что у меня есть ответ с точки зрения возможности выполнить то, что мне нужно для моего проекта без утечки памяти, но это все еще может быть проблемой для других.

ОБНОВЛЕНИЕ

К сожалению, использование JSON.stringify также не работает, потому что, как я и ожидал, шаг сериализации, похоже, выделяет ОЗУ, а не освобождает ее по завершении.Использование памяти увеличивается по мере того, как объекты викторины преобразуются в строки, а не при записи в базу данных.Таким образом, вся эта попытка изменилась, и в этот момент возникает проблема с памятью.Если объекты викторины преобразуются в строку без использования JSON.stringify, использование памяти остается неизменным во время преобразования, но увеличивается, когда строки записываются в базу данных.В обоих случаях память почти всегда освобождается, и это происходит только тогда, когда оператор delete port.quiz [c] не закомментирован;но выпуск происходит медленно, и использование памяти достигает высокого уровня, около 1 ГБ, для завершения задачи перед отбрасыванием, либо во время записи объектов викторины в базу данных, либо при преобразовании в строки.И в тех случаях, когда память не освобождается, обновление не освобождает ее, а только закрытие страницы.Если оператор delete port.quiz [c] закомментирован, использование памяти будет увеличиваться до тех пор, пока не произойдет сбой моей машины, на которой имеется только 4 ГБ ОЗУ.

Единственный способ, которым я могу записать большое количество опросов в базу данных.без проблем с использованием памяти, было создать отдельное хранилище объектов для каждого теста, а затем записать вопросы для каждого теста в этом хранилище.Объекты меньшего размера не вызывают проблемы с использованием памяти, если активен оператор delete port.quiz [c].Таким образом, я мог бы записывать большие объемы данных, не достигая половины использования памяти в предыдущих случаях.

Похоже, что этот вопрос не вызывает особого интереса;но если вы понимаете, почему это происходит и / или как это исправить, пожалуйста, объясните.Спасибо.

 <!DOCTYPE html> 

 <html lang="en">

 <head>

   <meta charset="utf-8"> 

   <script src="jquery-3.3.1.js"></script>

   <style>

     html { background-color: rgb(73,110,147); }


   </style>

 </head>


 <header>
 </header>


 <body>

   <div style='width: 750px; min-height: 100px; margin: 0 auto; border: 1px solid black; background-color: white;'>

     <p id="msg" style="margin: 25px auto; text-align: center;">IndexedDB Testing</p>

     <button id="build_port">Build Portfolio</button>

     <button id="write_quiz">Write Quiz</button>

   </div>


 </body>




 <script>

 "use strict";

 $(document).ready( function() { 



 var db, c = 0, port;


 $("#build_port").click( load_port );

 $("#write_quiz").click( function() { add_quiz(++c); } );


 function load_port() {

      $("#msg").text('Generating quiz portfolio');

      setTimeout( function() { port = gen_port(50); open_db(); }, 10 );

 } // close load_port




   function add_quiz(c) {

        var qz_tran = db.transaction( ["quiz data"], "readwrite" ),  

             store = qz_tran.objectStore("quiz data"),

             request;



           qz_tran.oncomplete = function(event) {

                $("#msg").text('Done writing quiz ' + c + ' to the database!');

           };


           qz_tran.onerror = function(event) {

                alert("Error Writing data " + event.target.errorCode + " .");

           };



        request = store.add( port.quiz[ c ], c );


        request.onsuccess = function( event ) { 

                  $('#msg').text( 'Writing quiz ' + c );

                  //delete port.quiz[ c ];

        };


        request.onerror = function( event ) { alert('write error : ' + event.target.errorCode ); };


   } // close add_quiz






      function open_db() {

           if ( !window.indexedDB ) {

               alert("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");

           }; // end if

           // Temporarily deleting each time for testing purposes.
           // Will later need to test for an existing databases just like did for localStorage.


           indexedDB.deleteDatabase("quizDB");


           // Attempt to open a database.

           var status = 'Existing',

               req = window.indexedDB.open( "quizDB", 1 );


           req.onerror = function(event) {

                alert('Attempt to open the portfolio database failed. Error code : ' + event.target.errorCode + '.' );

           }; // close onerror



           req.onsuccess = function(event) {

                db = this.result;

                db.onerror = function(event) {

                     // Generic error handler for all errors targeted at this database's requests!

                     alert( 'Database error: ' + event.target.errorCode + '.' );

                }; // close onerror


                $("#msg").text('opened/created database as : ' + status );


           }; // close onsuccess



           req.onupgradeneeded = function(event) { 

                var db = event.target.result;

                status = 'New';

                db.createObjectStore( "quiz data" );

           }; // close onupgradeneeded


       } // close open_db





      function gen_port(n) {

           var i, j, k, port = new Object(),

               t = new Array(5000).join('?');


           port.quiz = new Object();


           for ( i = 1; i <= n; i++ ) {

             port.quiz[i] = new Object();

             port.quiz[i].name = 'Quiz ' + i;

             port.quiz[i].question = new Object();


             for ( j = 1; j <= 500; j++ ) {

                port.quiz[i].question[j] = new Object();

                for ( k = 1; k <= 26; k++ ) {

                     port.quiz[i].question[j]['answer_choice_' + k] = new Object();

                     port.quiz[i].question[j]['answer_choice_' + k].text = 'Quiz ' + j + ', Question ' + j + ' AC ' + k + ' : ' + t;       

                     port.quiz[i].question[j]['answer_choice_' + k].checked = true;       

                     port.quiz[i].question[j]['answer_choice_' + k].pos = k;       

                }; // next k

             }; // next j question


           }; // next i quiz



           $("#msg").text('Done generationg quiz portfolio');

           return port;


      } // close gen_port






      function gen_port_2(n) {

           var i, j, k, port = new Object(),

               t = new Array(130000).join('?');


           port.quiz = new Object();


           for ( i = 1; i <= n; i++ ) {

             port.quiz[i] = new Object();

             port.quiz[i].name = 'Quiz ' + i;

             port.quiz[i].question = new Object();


             for ( j = 1; j <= 500; j++ ) {

                port.quiz[i].question[j] = new Object();

                port.quiz[i].question[j] = t;

             }; // next j question


           }; // next i quiz



           $("#msg").text('Done generationg quiz portfolio');

           return port;


      } // close gen_port










 }); // close document ready

 </script>


 </html>
...