electron-userland / electron-builder

A complete solution to package and build a ready for distribution Electron app with “auto update” support out of the box
https://www.electron.build
MIT License
13.64k stars 1.74k forks source link

autoUpdater does not fire download-progress event after multiple retries #7799

Open Brainshaker95 opened 1 year ago

Brainshaker95 commented 1 year ago

I noticed that the autoUpdater does not fire the download-progress event when retrying the download multiple times. I have broken down the issue already but I am not sure what the exact cause of this behavior is. I will try to explain with the following reduced example:

Note that in my real application the triggers are user controlled, this is just for demonstration

package.json ```json { "name": "update-test", "version": "0.0.1", "description": "This is a test", "main": "main.cjs", "scripts": { "build": "electron-builder -w --x64 --config ./electron.config.cjs" }, "dependencies": { "builder-util-runtime": "9.2.1", "electron-log": "4.4.8", "electron-updater": "6.1.4" }, "devDependencies": { "electron": "26.2.2", "electron-builder": "24.6.4" }, "repository": { "type": "git", "url": "git+https://github.com//update-test.git" } } ```
electron.config.cjs ```js /** * @type {import('electron-builder').Configuration} */ module.exports = { appId: 'com..update_test', productName: 'Update Test', icon: 'static/logo.png', asar: false, files: [ 'src/electron/main.cjs', 'src/electron/preload.cjs', ], }; ```
main.cjs ```js const path = require('path'); const { CancellationError } = require('builder-util-runtime'); const { app, BrowserWindow, ipcMain } = require('electron'); const log = require('electron-log'); const { autoUpdater, CancellationToken } = require('electron-updater'); log.transports.file.level = 'info'; autoUpdater.logger = log; autoUpdater.autoDownload = false; autoUpdater.disableWebInstaller = true; /** * @type {import('electron').BrowserWindow | null} */ let mainWindow = null; /** * @type {import('electron-updater').CancellationToken | null} */ let cancellationToken = null; const createMainWindow = () => { mainWindow = new BrowserWindow({ title: 'Update Test', show: false, webPreferences: { nodeIntegration: true, preload: path.join(__dirname, 'preload.cjs'), }, }); mainWindow .once('ready-to-show', () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } }) .on('close', () => { mainWindow = null; }); mainWindow.loadFile('index.html'); }; autoUpdater.on('checking-for-update', () => { mainWindow?.webContents.send('update-checking'); }); autoUpdater.on('update-available', (info) => { mainWindow?.webContents.send('update-available', info); }); autoUpdater.on('error', (error) => { log.error('error during update:', error); }); autoUpdater.on('update-cancelled', () => { mainWindow?.webContents.send('update-cancelled'); }); autoUpdater.on('download-progress', (progress) => { mainWindow?.webContents.send('download-progress', progress); log.info('download progress:', progress.percent); }); ipcMain.on('download-update', () => { cancellationToken = new CancellationToken(); autoUpdater .downloadUpdate(cancellationToken) .catch((error) => { if (error instanceof CancellationError) { return; } log.error('error during downloadUpdate:', error); }); }); ipcMain.on('cancel-update', () => { cancellationToken?.cancel(); }); ipcMain.on('check-for-update', () => { autoUpdater .checkForUpdates() .then((result) => { if (result?.cancellationToken) { ({ cancellationToken } = result); } }) .catch((error) => { if (error instanceof CancellationError) { return; } log.info('error during checkForUpdates', error); }); }); app .once('ready', createMainWindow) .on('window-all-closed', () => { app.quit(); }); ```
preload.cjs ```js const { ipcRenderer } = require('electron'); const delay = (ms = 1000) => new Promise((resolve) => { setTimeout(resolve, ms); }); delay(5000).then(() => ipcRenderer.send('check-for-update')); ipcRenderer.on('update-available', () => { ipcRenderer.send('download-update'); }); ipcRenderer.on('download-progress', () => { delay().then(() => ipcRenderer.send('cancel-update')); }); ipcRenderer.on('update-cancelled', () => { delay().then(() => ipcRenderer.send('download-update')); }); ```
main.log ```log [2023-09-26 21:58:30.273] [info] Checking for update [2023-09-26 21:58:31.185] [info] Found version 0.0.2 (url: Update-Test-Setup-0.0.2.exe) [2023-09-26 21:58:31.187] [info] Downloading update from Update-Test-Setup-0.0.2.exe [2023-09-26 21:58:31.190] [info] Download block maps (old: "https://github.com//releases/download/v0.0.1/Update-Test-Setup-0.0.1.exe.blockmap", new: https://github.com//releases/download/v0.0.2/Update-Test-Setup-0.0.2.exe.blockmap) [2023-09-26 21:58:31.497] [info] File has 31 changed blocks [2023-09-26 21:58:31.510] [info] Full: 70,661.57 KB, To download: 631.65 KB (1%) [2023-09-26 21:58:31.512] [error] Cannot download differentially, fallback to full download: Error: ENOENT: no such file or directory, open 'C:\Users\\AppData\Local\update-test-updater\installer.exe' [2023-09-26 21:58:32.791] [info] download progress: 33.79133869421877 [2023-09-26 21:58:33.791] [info] download progress: 68.37158578695076 [2023-09-26 21:58:34.306] [info] cancelled [2023-09-26 21:58:35.312] [info] Downloading update from Update-Test-Setup-0.0.2.exe [2023-09-26 21:58:35.315] [info] Download block maps (old: "https://github.com//releases/download/v0.0.1/Update-Test-Setup-0.0.1.exe.blockmap", new: https://github.com//releases/download/v0.0.2/Update-Test-Setup-0.0.2.exe.blockmap) [2023-09-26 21:58:35.594] [info] File has 31 changed blocks [2023-09-26 21:58:35.595] [info] Full: 70,661.57 KB, To download: 631.65 KB (1%) [2023-09-26 21:58:35.595] [error] Cannot download differentially, fallback to full download: Error: ENOENT: no such file or directory, open 'C:\Users\\AppData\Local\update-test-updater\installer.exe' [2023-09-26 21:58:36.902] [info] download progress: 34.259768690024316 [2023-09-26 21:58:37.904] [info] download progress: 68.90440721722504 [2023-09-26 21:58:38.313] [info] cancelled [2023-09-26 21:58:40.310] [info] Downloading update from Update-Test-Setup-0.0.2.exe [2023-09-26 21:58:40.311] [info] Download block maps (old: "https://github.com//releases/download/v0.0.1/Update-Test-Setup-0.0.1.exe.blockmap", new: https://github.com//releases/download/v0.0.2/Update-Test-Setup-0.0.2.exe.blockmap) [2023-09-26 21:58:40.587] [info] File has 31 changed blocks [2023-09-26 21:58:40.588] [info] Full: 70,661.57 KB, To download: 631.65 KB (1%) [2023-09-26 21:58:40.589] [error] Cannot download differentially, fallback to full download: Error: ENOENT: no such file or directory, open 'C:\Users\\AppData\Local\update-test-updater\installer.exe' ```

So basically it goes like this:

-> check for update

-> start download
-> download progress
-> cancel download

-> start download
-> download progress
-> cancel download

-> start download
=> process hangs and no download-progress event is emitted


I tracked it down to the electron-updater HttpExecutor ProgressCallbackTransform stream which seemingly stops sending progress updates after a while.
I tried adding the following log statement in this loop to visualize when exactly it stops:

  const log = require('electron-log');
  // ...
  for (const stream of streams) {
    if (stream instanceof ProgressCallbackTransform) {
      stream.on('data', () => log.info('data received'));
    }
    // ...
  }

I noticed that this stream does not receive any more data after retrying about 2 times (it's not always consistent for me), but it also doesn't throw an error or close. Is this some kind of intended behavior or have I ran into an edge case? Any help would be greatly appreciated. Let me know if I can help with any additional information.

github-actions[bot] commented 11 months ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.

Brainshaker95 commented 9 months ago

Can someone please provide some additional insights? Any help would be greatly appreciated.

mmaietta commented 8 months ago

@Brainshaker95 sorry, just now seeing this in my feed. How are you canceling the update? I can try investigating it locally if you're willing to provide a minimum reproducible repo?

Brainshaker95 commented 6 months ago

@mmaietta Sorry I only just noticed that I got an answer here...

This is the repo I used: https://github.com/Brainshaker95/update-test
There is also some other unnecessary junk in there since it originally came from a much larger project but the general repro is the same as documented here.
The project can be setup using yarn and yarn build:all. I am also happy to provide a new repository with only the bare minimum code to show the issue, but I would need some time for that. Let me know if this already suffices.

How are you canceling the update?

Using the cancel method on the cancellationToken passed to autoUpdater.downloadUpdate

github-actions[bot] commented 4 months ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.