electron-builder icon indicating copy to clipboard operation
electron-builder copied to clipboard

autoUpdater does not fire download-progress event after multiple retries

Open Brainshaker95 opened this issue 9 months ago • 5 comments

  • Electron-Builder Version: 24.6.4
  • Node Version: 20.7.0
  • Electron Version: 26.2.2
  • Electron Type (current, beta, nightly): current
  • Electron-Updater Version: 6.1.4
  • Target: nsis

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
{
  "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/<REDACTED>/update-test.git"
  }
}
electron.config.cjs
/**
 * @type {import('electron-builder').Configuration}
 */
module.exports = {
  appId: 'com.<REDACTED>.update_test',
  productName: 'Update Test',
  icon: 'static/logo.png',
  asar: false,
  files: [
    'src/electron/main.cjs',
    'src/electron/preload.cjs',
  ],
};
main.cjs
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
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
[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/<REDACTED>/releases/download/v0.0.1/Update-Test-Setup-0.0.1.exe.blockmap", new: https://github.com/<REDACTED>/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\<REDACTED>\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/<REDACTED>/releases/download/v0.0.1/Update-Test-Setup-0.0.1.exe.blockmap", new: https://github.com/<REDACTED>/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\<REDACTED>\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/<REDACTED>/releases/download/v0.0.1/Update-Test-Setup-0.0.1.exe.blockmap", new: https://github.com/<REDACTED>/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\<REDACTED>\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.

Brainshaker95 avatar Sep 26 '23 20:09 Brainshaker95

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.

github-actions[bot] avatar Nov 26 '23 00:11 github-actions[bot]

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

Brainshaker95 avatar Jan 02 '24 21:01 Brainshaker95

@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?

mmaietta avatar Feb 21 '24 01:02 mmaietta

@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

Brainshaker95 avatar Apr 17 '24 13:04 Brainshaker95