Show progress while a server side backup runs (+ fix output buffering bugs) - #60
Open
francescobragagna wants to merge 3 commits into
Open
Show progress while a server side backup runs (+ fix output buffering bugs)#60francescobragagna wants to merge 3 commits into
francescobragagna wants to merge 3 commits into
Conversation
Three problems with the file-redirecting mode of the Output class, all of them visible when running a server side backup: - ob_start() was called without a chunk size, so the whole dump was held in memory and only written to the file at the end. On a large database this hits memory_limit and the request dies with nothing to show for it. Flushing every 64 KB keeps memory flat and lets the file grow as it goes. - When the target file could not be opened (backup folder missing or not writable) the buffer was started anyway, so the error message the caller printed afterwards was swallowed by the callback and written nowhere. The user saw an empty page instead of the reason. Do not start buffering when there is no file to write to. - end() called gzclose() and then fell through to fclose() on the same handle, and ran ob_end_flush() again when invoked a second time by the destructor, ending a buffer it does not own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkPb2smVVLCiBbnTC3UcRZ
A server side backup submits the dialog form into its own iframe and the whole dump is written to a file, so nothing is sent to the browser until php is done. The dialog therefore sits unchanged for as long as the backup takes - minutes on a large database - with no way to tell whether anything is happening, and a proxy timeout in between simply looks like a hang. The request is now sent in the background and the dialog polls its progress through status.php, using the getModuleStatus() hook that is already there for upload progress: - lib/backupstate.php records the state of a running backup in tmp/, keyed by a token the dialog generates. Updates are throttled to one write every 0.4s and written through a scratch file, so a poll never reads a half written state. Stale files are removed on the next run. - modules/backup.php answers the poll and only reports on a backup started by the session that is asking, releasing the session lock right away so polling never queues behind the backup itself. - modules/download.php registers the token before releasing the session, counts the selected objects, and updates the state per object and per batch of rows. It answers json when a token is given. ignore_user_abort() keeps a backup alive when the browser goes away, and a shutdown handler records fatal errors so the dialog reports them instead of polling forever. - lib/export/export.php gained an optional per batch progress callback. The dialog now shows a progress bar with the current object, the object and row counters, the bytes written and the elapsed time, and ends with the file name, its size and the duration. Because the state lives in a file, the poll - not the request - decides when the backup is over: a dropped connection no longer loses the result. Selecting no object is refused up front instead of silently producing an empty file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkPb2smVVLCiBbnTC3UcRZ
Also moves the existing backup strings into $LANGUAGE_JS, as the javascript __() helper only reads from that array. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkPb2smVVLCiBbnTC3UcRZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
A server side backup submits the dialog form into its own iframe and writes the whole dump to a file, so nothing reaches the browser until php is done. The dialog sits unchanged for as long as the backup takes, with no way to tell whether anything is happening — and a proxy timeout in between is indistinguishable from a hang.
This adds progress reporting to the backup dialog, and fixes three bugs in
Outputfound along the way.Progress reporting
The request is sent in the background and the dialog polls
status.php, using thegetModuleStatus()hook already present for upload progress — no new endpoint, and the poll does not go throughindex.php(no MySQL connection per poll).lib/backupstate.php(new) keeps the state of a running backup intmp/, keyed by a token the dialog generates. Writes are throttled to one every 0.4s and go through a scratch file, so a poll never reads a half written state. Stale files are cleaned up on the next run.modules/backup.phpanswers the poll, reports only on a backup started by the session that is asking, and releases the session lock immediately.modules/download.phpregisters the token before releasing the session, counts the selected objects, and updates the state per object and per batch of rows.ignore_user_abort()keeps a backup alive when the browser goes away; a shutdown handler records fatal errors so the dialog reports them instead of polling forever.lib/export/export.phpgained an optional per batch progress callback.Because the state lives in a file, the poll decides when the backup is over, not the request: a connection dropped by a proxy no longer loses the result. Selecting no object is now refused uroducing an empty file.
Bug fixes in
Output(first commit, stands alone)ob_start()had no chunk size, so the entire dump w only at the end —memory_limiton a large database,and the request dies with nothing to show. Now flushes every 64 KB.end()calledgzclose()and then fell through to , and re-ranob_end_flush()when called a second timeby the destructor, ending a buffer it does not own.Testing
Manual, against MySQL 8.4 on PHP 8.2, on this branch:
CREATE TABLE, 120,000INSERT, footerpresent, no php noise in the file).
sysschema, 151 objects across tables, views, procedures, functions and triggers: completes and reports correctly.Notes
lang/it.phpis updated; the new strings fall back to English elsewhere.tmp/needs to be writable; it falls back to the system temp directory if it is not.