Очень странное поведение
Только что закончил отладку нового механизма сохранения данных в БД. Столкнулся с ужасной вещью, причины которой мне совсем не понятны.
В общем есть страница с таблицей для ввода данных, наверху есть трекер для переключания между блоками данных, внизу есть кнопка Сохранить, на неё повешен обработчик. Обработчик выполняет сохранение лишь тех полей, в которых побывал фокус (хранится всё это счастье в специальном массиве в виде строк запроса к PHP-скрипту). При клике по какому-либо квадратику трекера сценарий выводит confirm с вопросом о том, нужно ли сохранить данные перед переходом. Открыл параллельно в разных вкладках браузера свою систему и phpMyAdmin (для 100% проверки, глюк проявлялся и без этого). Кстати, после confirm-а появляется alert с сообщением о количестве выполненных запросов. Так вот, к делу. Происходит ужасная вещь: если нажать на кнопки ОК в confirm и alert довольно быстро (в пределах 3-х секунд), то внешне всё пройдёт гладко. Более того, данные в базе перепишутся (phpMyAdmin тому подтверждение). После этого случится запланированный редирект. Но вот после клика на изначальной кнопке трекера - по сути, это возврат к прежнему блоку, который реализован через отправку формы на тот же адрес в качестве action, используя скрытое поле, в котором хранится сдвиг для чтения из БД (как и первый переход, они равносильны) - произойдёт переход к прежнему блоку, но данные там будут... СТАРЫЕ! :blink: Я до сих пор не могу понять чё это за фигня. Самое интересное, что если перед подтверждением confirm-а выждать секунд 5-7, то баг не проявляется. Если пользоваться кнопкой сохранения снизу, а уж потом делать переход к другому блоку используя трекер, ничего подобного тоже нет. И самое главное, в IE7 я этого тоже не заметил. По логике вещей, такого быть не должно. P.S. Только что проверил, в Opera ситуация ещё того печальней. Даже длительное ожидание не помогает :( И в IE7 теперь кстати то же самое... Может, баги PHP-интерпретатора, который использует результаты старых MySQL запросов вместо того, чтобы делать новые? Но тогда почему вариант через кнопку Сохранить проходит на ура? :-? |
Куча текста и ничего не понятно, какие данные, что, откуда, для чего как... не понятно... куча букв и ничего не ясно.
|
ОК, сорри. Вот адрес, уже Бог знает который раз его пишу :)
http://popov654.pp.ru/livemarks Логин admin, пароль test Попробуйте сперва написать что-нибудь в любой чистой ячейке, например в базе Литература, потом кликнуть по бирюзовому квадратику (откроет чистую страницу засчёт выставления оффсета большего, чем кол-во записей в таблице БД), подтвердите сохранение. Пойдёт асинхронный запрос, правда "глупый", через объект Image(). Но успешный. Потом вернитесь обратно, кликнув по первому квадрату. Вуаля - всё осталось как было, но это только иллюзия, ибо просмотр в phypMyAdmin говорит об обратном. Можете даже туда же, только через другую вкладку зайти, повторно залогинившись. Изменения должны появиться Что-то где-то кэшируется?.. :-? |
Блин... Существенная поправочка: фокус со вторым логином на другой вкладке не поможет :blink:
Но в phpMyAdmin данные реально обновляются! Я к сожалению не могу дать Вам доступ чтобы показать... Могу скрин разве что выложить :( Вот Вы как думаете: теоретически может так быть, что данные остаются старыми только для моего скрипта?.. Ну я не знаю, веб-сервер же видит, к какому скрипту идёт обращение, да и PHP-интерпретатор тоже. Может кто-то из них чего-то сохраняет? Ну например интерпертатор мог каким-то образом старые переменные использовать вероятно... С результатами предыдущих SQL-запросов Я не вижу другого рационального объяснения... |
Ну кажется начало что-то прорисовывается. При варианте, когда я сохраняю через кнопку снизу, Опера например секунд 6-7 чего-то "грузит" даже после алерта, хотя алерт идёт по коду уже после назначения нужных URL картинкам. Вот код:
function saveChanges() { var imgs = new Array(); for (var i = 0; i < queries.length; i++) { imgs.push(new Image()) imgs[i].src = queries[i] } setTimeout('queries = []', 100) alert('Данные сохранены! Выполнено запросов: ' + queries.length) return false; } Возможно, запросы-то и уходят гораздо раньше, но для браузера этот таймаут важен, чтобы поменять статус готовности документа и запомнить состояние полей формы. А если, не дождавшись этого, перенаправить его скриптом куда-нибудь, то почему-то при возврате он отображает не то, что есть на самом деле. |
чисто предположение, може autocomplete в инпутах виновен?
Когда назад возвращаетесь, подгружаются какие-нибудь данные или станица? |
Блин, я идиот. Вопрос снимается.
У меня просто не успевали отправиться ВСЕ запросы, а поскольку тип работы определялся лишь по одному ученику... Короче, этому ученику периодически "не везло". По логам веб-сервера проверил. Ну а то, что я видел в phpMyAdmin - это как раз и были те 7-10 записей из 19, что сохранились. Вопрос: а как сделать чтобы гарантированно убедиться, что все запросы выполнены? Только XMLHttpRequest? |
Ау... :(
|
Обобщу вопрос: вот есть у меня массив запросов. Ну пока это Image и я проверяю свойство complete, которое должно характеризовать завершение загрузки, причём я даже настроил PHP скрипт так, чтобы он реально, обработав запрос, отдавал изображение 1х1 пиксель.
Если у меня будет XMLHttp, то там будет onReadyStateChange... Вопрос в другом. Вот есть у меня массив этих объектов, и пока все запросы не отправлены полностью, дальнейшее выполнение кода нужно ЗАБЛОКИРОВАТЬ, чтобы браузер не ушёл на другой адрес. Поскольку обычный setTimeout(), как здесь уже обсуждалось, не останавливает поток выполнения, я использую логическую переменную. Но получается глупо: как только один запрос отправится, она устанавливается в true. Если есть ещё хотя бы один незавершённый запрос, его "прослушиватель", повешенный на setInterval(), конечно перепишет переменную на false... Но во-первых, он может тупо не успеть, и функция, повешенная на другой таймер и ожидающая разрешения на переход, может сработать. Это первая проблема. А вторая проблема видимо в том, что я криворукий - у меня после реализации вышеописанного вообще запросы перестали отправляться :( Где я слажал? |
Есть ещё мысль повесить на setInterval полный обход массива... Но мне это кажется каким-то страшно нерациональным. Ведь если большая часть запросов давно отправлена, мы будем гулять по массиву зря (следовательно, потеря производительности, т.к. запросов может быть до тучи).
Заводить ещё один массив для хранения индексов успешно отправленных - тоже вроде как глупо, по понятным причинам... |
popov654, тут очень много текста. не могли бы вы описать проблему в двух предложениях, с кодом ?)
|
Цитата:
Цитата:
Я убрал лишний текст (сорри за повтор). Код приводить вряд ли есть смысл: там ровно то, что написано выше, только ещё менее наглядно (например, потому что логических флага зачем-то сделано два). |
Вот код функции посылки запросов:
function saveChanges() { var imgs = new Array(); for (var i = 0; i < queries.length; i++) { imgs.push(new Image()) imgs[i].src = queries[i] } var tries = 0; var timer = setInterval(function() { tries++ requestComplete = true for (var i = 0; i < imgs.length; i++) { if (!imgs[i].complete) { requestComplete = false } } if (requestComplete) { clearInterval(timer); alert('Данные сохранены! Запросов выполнено: ' + queries.length) queries = [] } else if (tries > 8) { clearInterval(timer); } }, 100); return false; } var requestComplete = false А вот код, который выполняет ожидание перед сменой блока: elem.onclick = function() { if (queries.length && confirm('Вы хотите сохранить введённые данные?')) { saveChanges() var tries = 0; var timer = setInterval(function() { tries++ if (requestComplete) { requestComplete = false clearInterval(timer) document.form1.action = 'edit.php' document.form1.from.value = pages * 25 document.form1.submit() } if (tries > 10) { clearInterval(timer) alert('Ошибка запроса') } }, 200); } else { document.form1.action = 'edit.php' document.form1.from.value = pages * 25 document.form1.submit() } } Этот блок кода отвечает за переадресацию на последний блок данных, поэтому замыкания тут не используются. Я отредактировал неудачные на мой взгляд места, это исправленная версия. Однако на длинных запросах (29 записей сразу например) почему-то вылетает алерт с неудачной отправкой (при одиночных запросах всё ОК). Добавлено спустя 5 минут: Всё! Работает! Я всего лишь увеличил таймауты, и этого оказалось достаточно. Вот итоговый код: function saveChanges() { var imgs = new Array(); for (var i = 0; i < queries.length; i++) { imgs.push(new Image()) imgs[i].src = queries[i] } var tries = 0; var timer = setInterval(function() { tries++ requestComplete = true for (var i = 0; i < imgs.length; i++) { if (!imgs[i].complete) { requestComplete = false } } if (requestComplete) { clearInterval(timer); alert('Данные сохранены! Запросов выполнено: ' + queries.length) queries = [] } else if (tries > 8) { clearInterval(timer); } }, 500); return false; } var requestComplete = false elem.onclick = function() { if (queries.length && confirm('Вы хотите сохранить введённые данные?')) { saveChanges() var tries = 0; var timer = setInterval(function() { tries++ if (requestComplete) { requestComplete = false clearInterval(timer) document.form1.action = 'edit.php' document.form1.from.value = pages * 25 document.form1.submit() } if (tries > 3) { clearInterval(timer) alert('Ошибка запроса') } }, 4000); } else { document.form1.action = 'edit.php' document.form1.from.value = pages * 25 document.form1.submit() } } |
Чёрт... Рано обрадовался. Два столбца по 29 запросов уже не тянет. А должна тянуть максимум 25 таких столбцов!
Самое обидное, что пока я не ввёл эти проверки, запросы шли быстрее раз так в пять. :( Так и знал, что линейный обход - ужасный способ. И что мне теперь делать? Может, ничего не проверяя, поставить таймаут побольше (секунд 10)? Но тогда ждать юзеру придётся даже при маленьких модификациях базы, даже когда запрос всего один :( |
вы проверяете завершенность по интервалу - ай ай ай. :nono:
function saveChanges(callback) { var imgs = new Array(), i = 0, max = queries.length - 1; var timer = setInterval(function(){ imgs.push(new Image); // обработчик загрузки для последней картинки if (i === max) imgs[i].onload = callback( imgs ); imgs[i].src = queries[i]; if( ++i > max ) clearInterval(timer); }, 100); } elem.onclick = function() { // для экономии места повторяющиеся операции занёс в функцию function normForm() { var a = document.form1; a.action = 'edit.php'; a.from.value = pages * 25 a.submit(); }; if (queries.length && confirm('Вы хотите сохранить введённые данные ? ')) { saveChanges(/*выполнится по завершении запроса*/function(a) { // a - массив картинок. alert('Данные сохранены! Запросов выполнено : '+a.length); queries = []; normForm(); }); } else { normForm(); } } |
Спасибо) Не сообразил, что onload есть не только у body :)
Туплю) P.S. Там код этого обработчика несколько сложнее (потому что при нажатии на разные кнопки вызываются разные блоки), но я допилю, всё ОК :) |
Жаль, плюсик Вам в карму не отправить :)
|
Да ёлки-палки... :-E
После такой переделки у меня теперь та же ситуация, что была в самом начале: пишется, но не всё. Я изучил лог-файл сервера. В большинстве случаев не хватает одного запроса, иногда один запрос отправляется на адрес undefined. Странно... |
Ну я так и думал. Надо было поменять эти две строки местами:
if (i == max) imgs[i].onload = callback( imgs ); imgs[i].src = queries[i]; Походу до присваивания src интерпретатор считал рисунок загруженным (как раз по адресу undefined), и запускался callback, очищающий весь массив адресов запроса. В итоге запрос либо не уходил вовсе, либо уходил на адрес undefined. Настораживает ещё вот какой факт: иногда помимо запроса undefined не хватало ещё одного запроса. Поэтому вопрос к Вам: Вы уверены, что все предыдущие запросы успеют дойти? Обработчик-то мы привязали только к последнему :) |
как они могут не дойти? картинки создаются в цикле - значит запросы так же создаются в цикле. посмотрите вкладку Network в инструменте веб-мастера
|
Ну мало ли, кто-то будет создаваться дольше... Я на Java уже писал сетевое приложение, реально с этим делом сталкивался. Правда там запросы создавались в разных потоках... :)
|
Всё! Победа! Наконец-то всё работает так, как оно и должно работать в идеале. Теперь я ставлю в ближайшей перспективе такие две цели:
1) Сделать красивый анимированный прогрессбар (сам прогрессбар уже есть), который будет отображать РЕАЛЬНЫЙ ход выполнения запроса 2) Сделать передачу методом POST всех строк сразу (либо в формате JSON, либо "склеив" их вручную). Это должно дать существенный выигрыш в скорости + избавить мои бедные логи от замусоривания бесконечными запросами к одному и тому же PHP-скрипту. Есть только одна проблема: вторая задача слегка перечёркивает первую, поскольку в этой ситуации отследить реальную степень готовности вряд ли возможно... Кто что думает на этот счёт? :) |
Ребят, ну подскажите кто-нибудь)
Я же ведь пока делать ничего не могу, пока не определюсь, надо ли GET на POST будет заменять... :) |
Цитата:
|
:D
У меня вначале POST использовался) Только передавалась всегда вся форма, что было весьма нерационально. Плюс скрипт записи был довольно сложный. Теперешний способ даёт крайне хорошие результаты при маленьких изменениях (1-29 запросов к БД), но совершенно чудовищные показатели при запросах больших, когда требуется проапдейтить хотя бы полтаблицы (и это при том, что я ВСЮ таблицу сократил в два раза!) Только что проверял такую масштабную модификацию, это адова жесть. 20-25 секунд ожидания даже на локалке (!) Я уже не говорю про бедные лог-файлы Апача - ничего себе 580 запросов за одно редактирование :) Там кстати после всей этой радости ещё запускается маленький cleanup таблицы для совместимости со старыми версиями (удалить записи-"пустышки"). В итоге около 30 секунд приходится ждать до завершения. При сохранении при помощи кнопки снизу один раз браузер и вовсе завис в состоянии загрузки, уже после алерта с успешным завершением :( Так что, видимо, буду переделывать. Однако, смущает проблема с индикацией: если я сделаю POST, невозможно будет отследить прогресс запроса. Или всё-таки есть способ это сделать? |
Вам видимо доставляет большое удовольствие писать такие большие сообщения )) мне аж читать лень... :)
|
1. Вообще это должно делаться методом POST. Уже потому что для сохранения данных этот метод и предназначен. POST и PUT. Метод GET предназначен для получения данных. Что мешает передавать на сервер не всю форму, а лишь измененные записи? Id: value.
2. Это должно происходить одним запросом, а не каждый запрос на одну запись. Это настолько нелепо и ресурсоемко, как сделано сейчас, что я не понимаю даже, как ты еще сам не догадался, что что-то в твоем методе не то. 3. Использовать AJAX, а не image.src. В принципе, это автоматом, если перепишешь на POST. 4. Цитата:
|
Да вот как видите, пока не сделал, не догадался. :( Кто же мог подумать, что это так долго будет происходить?
Хотя в принципе этого и следовало ожидать, 500-1000 запросов, 40 миллисекунд таймаут... Получаем 20-40 секунд времени на выполнение. Из плюсов - ну во-первых всю форму не грузить когда пару оценок поменяли. Это вроде объективно. Плюс код серверного скрипта, который вносит данные в базу, сократился до 8 строк (10 с небольшим с учётом всяких cleanup-ов). Было раза в 3 больше кода, кроме того, тот код был очень плохой (из-за плохой сопровождаемости, например он был чувствителен к ширине таблицы, которая задавалась в нём жёстко). Ещё что было плохо - ну например, куча лишних записей в базе, не несущих никакой смысловой нагрузки, т.е. пустых, записываемых, что называется, "для симметрии". А самое главное, я хотел попробовать AJAX в деле. Или передача данных через картинки - это не AJAX? Вроде бы это в учебнике в том же разделе описывалось... Ну если время сократится так значительно, можно разумеется и без индикации обойтись. Впрочем, в случае загрузки файла я тоже не знаю как индикацию делать... Вот ВКонтакте это сделали - интересно, как) |
Кстати, я сделал индикатор прогресса. Работает красиво, но код пока не очень хороший, поскольку я что-то налажал с получением координат элементов и вынужден был прописать числовые значения, что считается плохой практикой. Впрочем, сойдёт пока, всё равно от этого отказаться придётся потом :)
|
Старая версия будет доступна пока по адресу http://popov654.pp.ru/livemarks_old/login.html
|
Походу какой-то странный облом с методом POST... Какая-то странная ошибка:
[07.09.2011 20:24:38] JavaScript - http://popov654.pp.ru/livemarks/edit.php Event thread: click Uncaught exception: DOMException: INVALID_STATE_ERR Error thrown at line 444, column 5 in saveChanges(callback) in http://popov654.pp.ru/livemarks/edit.php: req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); called from line 429, column 9 in <anonymous function: document.form1.save.onclick>() in http://popov654.pp.ru/livemarks/edit.php: saveChanges(function() {}) Ну и почему?.. Всё ведь правильно сделал :( |
Убрал эту строчку, это дало возможность отладить PHP-скрипт. Но без неё скрипт не видит данные :cray:
|
А с ней опять ошибка та же самая...
|
А, блин. Почитал, разобрался. Следовало сначала метод open() вызвать у XMLHttpRequest, а потом уже заголовок ставить :D
|
Да копать его колотить... :-E
Теперь русские буквы приходят в битом виде. Причём они ещё в БД в таком виде заносятся. Самое забавное, кодирование с помощью encodeURIComponent() ровным счётом ничего не даёт. На стороне сервера всё раскодируется само, даже не нужно вызывать urldecode(), а вот в базу всё равно пишется чёрт знает как. |
Фуууф...
Вот эти 3 строки в конфиге были причиной всех несчастий: Код:
mysql_query("SET character_set_client='cp1251'"); Чтобы не рисковать остальными модулями, добавил сразу после include "config.php" следующие 2 строчки: Код:
mysql_query("SET character_set_client='utf8'"); Добавлено спустя 2 минуты: кстати, нижняя, как оказалось, вообще не нужна. Что вполне логично: она отвечает за передачу данных между PHP и MySQL |
Кстати, этот инклюд попортил мне ещё массу нервов, когда переменная с безобидным именем $base (номер предмета) вдруг начала чудесным образом менять значение с 3 на livemarks. И что бы вы думали? Правильно, она использовалась в config.php для хранения имени базы данных, а я имел неосторожность произвести чтение значения из $_POST ДО инклюда :lol:
|
УРАААА! Вышла версия 4.3 с новыми улучшенными алгоритмами сохранения данных! Подробнее тут: LiveMarks 4.3
|
Часовой пояс GMT +3, время: 11:19. |